前言OpenSSL 命令行开始确定OpenSSL版本和配置构建OpenSSL检查可用命令构建信任存储手动转换密钥和证书管理密钥生成创建证书签名请求从现有证书创建CSR无人值守CSR生成签署您自己的证书创建对多个主机名有效的证书检查证书检查公共证书密钥和证书转换PEM和DER转换PKCS #12(PFX)转换PKCS #7 转换配置获取支持的套件了解安全级别配置TLS 1.3配置OpenSSL默认值建议的套件配置生成DH参数旧套件配置关键字组合关键字构建密码套件列表关键字编辑器排序处理错误性能创建私有证书颁发机构功能和限制创建根CA根CA配置根CA目录结构根CA生成数据库文件的结构根CA操作创建OCSP签名证书创建下级CA下级CA配置下级CA生成下属CA操作使用OpenSSL测试TLS自定义编译OpenSSL以进行测试连接到TLS服务证书验证测试升级到TLS的协议提取远程证书测试协议支持测试密码套件配置测试密码套件首选项测试命名组测试DANE测试会话恢复在连接之间保持会话状态检查OCSP吊销测试OCSP装订检查CRL吊销测试重新协商检测心脏出血确定Diffie-Hellman参数的强度
尽管有缺点,OpenSSL是最成功、最重要的开源项目之一。它之所以成功,是因为它被广泛使用;它很重要,因为互联网基础设施的大部分安全都依赖于它。该项目包括密钥加密算法的高性能实现、完整的TLS和PKI堆栈以及命令行工具包。我认为可以肯定地说,如果你的工作与安全、web开发或系统管理有关,那么至少在某种程度上,你不可避免地要处理OpenSSL。互联网的大部分由开源产品提供支持,其中大多数依赖于OpenSSL。
本书介绍了使用OpenSSL的两种方法:
这两章都是从我的大型作品《Bulletproof TLS and PKI》中借来的。我决定将OpenSSL章节作为单独的免费书籍出版,因为严重缺乏良好且易于获取的文档。对于复杂和长期存在的项目来说,你在互联网上找到的OpenSSL文档往往是错误和过时的。
此外,出版商经常放弃一个或多个章节来展示这本书的样子,我认为我应该充分利用这种做法,不仅要让OpenSSL章节免费,还要承诺随着时间的推移继续维护和改进它们。所以他们来了。
OpenSSL是世界上使用最广泛的传输层安全(Transport Layer Security,TLS)协议的实现。在核心上,它也是一个健壮的、高性能的加密库,支持广泛的加密原语(primitives)。除了库代码之外,OpenSSL还提供了一组用于各种用途的命令行工具,包括对常见PKI操作和TLS测试的支持。
OpenSSL是这一领域事实上的标准,具有悠久的历史。该代码最初于1995年以SSLeay的名字开始使用(SSLeay名字中的字母“eay”是Eric A.Young的缩写。),当时由Eric A.Young和Tim J.Hudson开发。OpenSSL作为一个单独的项目诞生于1998年,当时Eric和Tim决定开始开发一个名为BSAFE SSL-C的商业SSL/TLS工具包。开发人员社区拿起了该项目,并继续维护它。
今天,OpenSSL在服务器端和许多客户端程序中无处不在。命令行工具也是密钥和证书管理的最常见选择。就浏览器而言,OpenSSL也有很大的市场份额,尽管是通过谷歌的分支,称为 BoringSSL 。
OpenSSL过去在OpenSSL和SSLeay许可证下是双重许可的。两者都类似BSD,有广告条款。在2021年9月发布的3.0版本中,OpenSSL通过迁移到Apache License v2.0简化了其许可。
如果您使用的是Unix平台之一,那么开始使用OpenSSL应该很容易;实际上,您可以保证已经在系统上安装了它。然而,事情可能会出差错。例如,您可能有一个不正确的版本,或者可能有其他工具(例如,LibreSSL)配置为在调用OpenSSL时响应。由于这个原因,最好首先检查您已经安装了什么,并且只有在绝对必要时才使用自定义安装。另一种选择是寻找打包平台。例如,对于OS X,您可以使用Brew或MacPorts。一如既往,从头开始编译东西很少是问题;无限期地维护该软件才是。
在本章中,我假设您使用的是Unix平台,因为这是OpenSSL的自然环境。在Windows上,从头编译软件不太常见,因为工具不容易获得。您仍然可以自己编译OpenSSL,但可能需要更多的工作。或者,您可以考虑从Shining Light Productions网站下载二进制文件。如果您从多个网站下载二进制文件,则需要确保它们不是在不同版本的OpenSSL下编译的。如果是,您可能会遇到难以排除故障的崩溃。最好的方法是使用单个程序包,其中包含您需要的所有内容。例如,如果要在Windows上运行Apache,可以从Apache Lounge网站获取二进制文件。
在你做任何工作之前,你应该知道你将使用哪个OpenSSL版本。TLS和PKI继续以相当快的速度发展,如果你的OpenSSL版本不支持它们,你可能会发现你能做的事情是有限的。以下是我在Ubuntu 20.04 LTS上使用openssl版本获得的版本信息,这是我将在本章示例中使用的系统:
$ openssl version
OpenSSL 1.1.1f 31 Mar 2020
在撰写本文时,OpenSSL 1.1.1是生产中使用的主要分支,具有所有优秀的功能。在较旧的系统上,您可能会发现1.1.0分支的版本,这很好,因为它可以与TLS 1.2安全地使用,但它不支持TLS 1.3等现代功能。另一个方向是OpenSSL 3.0,它引入了库的重大更新,进行了实质性的架构更改,并切换到Apache License 2.0,以更好地与其他程序和库互操作。命令行工具,即我在本章和下一章中介绍的工具,应该基本相同。也就是说,每个版本,尤其是主要版本,都很可能以微妙的方式改变工具的行为。当您从一个分支更改到另一个分支时,有必要查看更改文档以了解可能存在的差异。
xxxxxxxxxx
笔记
虽然你从版本号上看不出来,但各种操作系统通常不会实际发布确切的官方OpenSSL版本。通常情况下,它们包含为特定平台定制或修补以解决各种已知问题的分叉。然而,版本号通常保持不变,没有迹象表明该代码是原始项目的分支,可能具有不同的功能。如果你注意到一些意想不到的事情,请记住这一点。
要获取完整的版本信息,请使用 -a
开关:
xxxxxxxxxx
$ openssl version -a
OpenSSL 1.1.1f 31 Mar 2020
built on: Mon Apr 20 11:53:50 2020 UTC
platform: debian-amd64
options: bn(64,64) rc4(16x,int) des(int) blowfish(ptr)
compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -Wa,--noexecstack -g -O2 -fdebug-prefix-map=/build/openssl-P_ODHM/openssl-1.1.1f=. -fstack-protector-strong -Wformat -Werror=format-security -DOPENSSL_TLS_SECURITY_LEVEL=2 -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DRC4_ASM -DMD5_ASM -DAESNI_ASM -DVPAES_ASM -DGHASH_ASM -DECP_NISTZ256_ASM -DX25519_ASM -DPOLY1305_ASM -DNDEBUG -Wdate-time -D_FORTIFY_SOURCE=2
OPENSSLDIR: "/usr/lib/ssl"
ENGINESDIR: "/usr/lib/x86_64-linux-gnu/engines-1.1"
Seeding source: os-specific
我想你最初不会觉得这个输出很有趣,但知道在哪里可以找到你的OpenSSL是如何编译的是很有用的。特别有趣的是 OPENSSLDIR
设置,在我的例子中,它指向 /usr/lib/ssl ;它将告诉您OpenSSL在哪里查找其默认配置和根证书。在我的系统上,该位置基本上是 /etc/ssl 的别名,这是Ubuntu PKI相关文件的主要位置:
xxxxxxxxxx
lrwxrwxrwx 1 root root 14 Apr 20 11:53 certs -> /etc/ssl/certs
drwxr-xr-x 2 root root 4096 May 14 21:38 misc
lrwxrwxrwx 1 root root 20 Apr 20 11:53 openssl.cnf -> /etc/ssl/openssl.cnf
lrwxrwxrwx 1 root root 16 Apr 20 11:53 private -> /etc/ssl/private
misc/ 文件夹包含一些补充脚本,其中最有趣的是允许您实现私有证书颁发机构(CA)的脚本。你可能会也可能不会最终使用它,但在本章的后面,我将向你展示如何从头开始做同样的工作。
在大多数情况下,您将使用系统提供的OpenSSL版本,但有时有充分的理由使用较新或较旧的版本。例如,如果您有一个较旧的系统,它可能会使用不支持TLS 1.3的OpenSSL版本。另一方面,较新的OpenSSL版本可能不支持SSL 2或SSL 3。虽然这在一般情况下是正确的做法,但如果你的工作是测试系统的安全性,你需要支持这些旧功能。
您可以从下载最新版本的OpenSSL(在我的例子中是1.1.1g)开始:
xxxxxxxxxx
$ wget https://www.openssl.org/source/openssl-1.1.1g.tar.gz
下一步是在编译之前配置OpenSSL。为此,您通常会使用配置脚本,该脚本首先尝试猜测您的架构,然后运行整个配置过程:
xxxxxxxxxx
$ ./config \
--prefix=/opt/openssl \
--openssldir=/opt/openssl \
no-shared \
-DOPENSSL_TLS_SECURITY_LEVEL=2 \
enable-ec_nistp_64_gcc_128
自动架构检测有时可能会失败(例如,在OS X上使用旧版本的OpenSSL),在这种情况下,您应该使用显式架构字符串调用Configure脚本。配置语法在其他方面是相同的。
除非您确定不想这样做,否则必须使用 --prefix
选项将OpenSSL安装到与系统提供的版本不冲突的私有位置。如果出错,可能会损坏您的服务器。另一个重要的选择是不共享,它强制静态链接并制作自包含的命令行工具。如果不使用此选项,则需要使用 LD_LIBRARY_PATH
配置才能使工具工作。
编译OpenSSL 1.1.0或更高版本时, OPENSSL_TLS_SECURITY_LEVEL
选项配置默认安全级别,该级别为所有库用户建立默认的最低安全要求。在编译时设置此值非常有用,因为它可用于防止配置错误。我将在本章稍后更详细地讨论安全级别。
enable-ec_nistp_64_gcc_128
参数激活某些常用椭圆曲线的优化版本。这种优化依赖于无法自动检测到的编译器功能,这就是为什么默认情况下它被禁用的原因。OpenSSL wiki上提供了完整的配置选项集。
xxxxxxxxxx
笔记
编译软件时,熟悉编译器的默认配置非常重要。系统提供的软件包通常使用各种强化选项进行编译,但如果您自己编译一些软件,则无法保证会使用相同的选项。
如果你正在编译1.1.0之前的版本,你需要先构建依赖关系:
xxxxxxxxxx
$ make depend
OpenSSL 1.1.0及以上版本将自动执行此操作,因此您可以使用以下内容继续构建主包:
xxxxxxxxxx
$ make
$ make test
$ sudo make install
您将在 /opt/openssl 中获得以下内容:
xxxxxxxxxx
drwxr-xr-x 2 root root 4096 Jun 3 08:49 bin
drwxr-xr-x 2 root root 4096 Jun 3 08:49 certs
drwxr-xr-x 3 root root 4096 Jun 3 08:49 include
drwxr-xr-x 4 root root 4096 Jun 3 08:49 lib
drwxr-xr-x 6 root root 4096 Jun 3 08:48 man
drwxr-xr-x 2 root root 4096 Jun 3 08:49 misc
-rw-r--r-- 1 root root 10835 Jun 3 08:49 openssl.cnf
drwxr-xr-x 2 root root 4096 Jun 3 08:49 private
private/ 文件夹为空,但这是正常的;您还没有任何私钥。另一方面,您可能会惊讶地发现 certs/ 文件夹也是空的。OpenSSL不包含任何根证书;维护信任存储被认为不在项目范围内。幸运的是,您的操作系统可能已经附带了可以立即使用的信任存储。以下内容在我的服务器上运行:
xxxxxxxxxx
$ cd /opt/openssl
$ sudo rmdir certs
$ sudo ln -s /etc/ssl/certs
OpenSSL是一个加密工具包,由许多不同的实用程序组成。我在我的版本中数了48。如果有一个合适的时机使用密码学中的“瑞士军刀”一词,那就是现在。即使你只会使用少数实用程序,你也应该熟悉所有可用的工具,因为你永远不知道未来可能需要什么。
要了解所提供的内容,只需请求帮助:
xxxxxxxxxx
$ openssl help
help
输出的第一部分列出了所有可用的实用程序。要获取有关特定实用程序的更多信息,请使用man命令,后跟实用程序的名称。例如,man ciphers
将为您提供有关密码套件配置方式的详细信息。然而, man openssl-ciphers
也应该能用:
xxxxxxxxxx
Standard commands
asn1parse ca ciphers cms
crl crl2pkcs7 dgst dhparam
dsa dsaparam ec ecparam
enc engine errstr gendsa
genpkey genrsa help list
nseq ocsp passwd pkcs12
pkcs7 pkcs8 pkey pkeyparam
pkeyutl prime rand rehash
req rsa rsautl s_client
s_server s_time sess_id smime
speed spkac srp storeutl
ts verify version x509
帮助输出实际上并没有到此为止,但其余的部分就没那么有趣了。在第二部分中,您将获得消息摘要(message digest)命令列表:
xxxxxxxxxx
Message Digest commands (see the `dgst' command for more details)
blake2b512 blake2s256 gost md4
md5 rmd160 sha1 sha224
sha256 sha3-224 sha3-256 sha3-384
sha3-512 sha384 sha512 sha512-224
sha512-256 shake128 shake256 sm3
然后在第三部分中,您将看到所有密码(cipher)命令的列表:
xxxxxxxxxx
Cipher commands (see the `enc' command for more details)
aes-128-cbc aes-128-ecb aes-192-cbc aes-192-ecb
aes-256-cbc aes-256-ecb aria-128-cbc aria-128-cfb
aria-128-cfb1 aria-128-cfb8 aria-128-ctr aria-128-ecb
aria-128-ofb aria-192-cbc aria-192-cfb aria-192-cfb1
aria-192-cfb8 aria-192-ctr aria-192-ecb aria-192-ofb
aria-256-cbc aria-256-cfb aria-256-cfb1 aria-256-cfb8
aria-256-ctr aria-256-ecb aria-256-ofb base64
bf bf-cbc bf-cfb bf-ecb
bf-ofb camellia-128-cbc camellia-128-ecb camellia-192-cbc
camellia-192-ecb camellia-256-cbc camellia-256-ecb cast
cast-cbc cast5-cbc cast5-cfb cast5-ecb
cast5-ofb des des-cbc des-cfb
des-ecb des-ede des-ede-cbc des-ede-cfb
des-ede-ofb des-ede3 des-ede3-cbc des-ede3-cfb
des-ede3-ofb des-ofb des3 desx
rc2 rc2-40-cbc rc2-64-cbc rc2-cbc
rc2-cfb rc2-ecb rc2-ofb rc4
rc4-40 seed seed-cbc seed-cfb
seed-ecb seed-ofb sm4-cbc sm4-cfb
sm4-ctr sm4-ecb sm4-ofb
OpenSSL不附带受信任的根证书集合(也称为根存储——root store 或信任存储——trust store ),因此如果您从头开始安装,则必须在其他地方找到它们。一种可能是使用操作系统中内置的信任存储,正如我之前所示。这种选择通常很好,但内置的信任存储可能并不总是最新的。此外,在混合环境中,各种系统中的默认存储之间可能存在有意义的差异。一个一致且可能更好的选择——但需要更多的工作——是重用(reuse)Mozilla的工作。Mozilla投入了大量精力来维护一个透明且最新的根存储,以供Firefox使用。
因为它是开源的,Mozilla将信任存储保存在源代码存储库中:
https://hg.mozilla.org/releases/mozilla-beta/file/tip/security/nss/lib/ckfw/builtins/certdata.txt
不幸的是,它的证书集合是专有格式的,对其他人来说并没有多大用处。如果你不介意通过第三方获取集合,Curl项目提供了一个定期更新的隐私增强邮件(Privacy-Enhanced Mail,PEM)格式的转换,你可以直接使用:
http://curl.haxx.se/docs/caextract.html
如果你更愿意直接与Mozilla合作,你可以使用Curl项目使用的相同工具转换它的数据。您将在下一节中找到有关它的更多信息。
xxxxxxxxxx
笔记
如果你想编写自己的转换脚本,请注意Mozilla的根证书文件不是一个简单的证书列表。虽然大多数证书都被认为是可信的,但也有一些证书被明确禁止。此外,某些证书可能仅被视为对某些类型的使用是可信的。我在这里描述的Perl脚本足够聪明,可以知道其中的区别。
此时,您拥有的是一个根存储,其中所有受信任的证书都在同一个文件中。如果你只打算将其与 s_client
工具一起使用,这将很好地工作。在这种情况下,您需要做的就是将 -CAfile
开关指向您的根存储。更换服务器上的根存储将需要更多的工作,具体取决于使用的操作系统。
例如,在Ubuntu上,您需要替换 /etc/ssl/certs 文件夹的内容。Ubuntu附带了一个名为 update-ca-certificates
的工具,该工具可能有效。或者,您可以通过复制现有数据的结构手动进行更改。从外观上看,该文件夹包含作为单独文件的受信任证书,以及所有这些证书在一个名为 ca-certificates.crt 的文件中。您还将观察到一些符号链接;它们是由OpenSSL的 rehash
或 c_rehash
工具创建的。任何手动更改的缺点是,当系统更新时,它们可能会被覆盖。
为了转换Mozilla的根存储,Curl项目使用了最初由Guenter Knauf编写的Perl脚本。此脚本是Curl项目的一部分,但您可以通过以下链接直接下载:
https://raw.githubusercontent.com/curl/curl/master/lib/mk-ca-bundle.pl
下载并运行脚本后,它将从Mozilla获取证书数据并将其转换为PEM格式:
xxxxxxxxxx
$ ./mk-ca-bundle.pl
SHA256 of old file: 0
Downloading certdata.txt ...
Get certdata with curl!
[...]
Downloaded certdata.txt
SHA256 of new file: cc6408bd4be7fbfb8699bdb40ccb7f6de5780d681d87785ea362646e4dad5e8e
Processing 'certdata.txt' ...
Done (138 CA certs processed, 30 skipped).
如果保留以前下载的证书数据,脚本将使用它来确定更改的内容,并仅处理更新。
大多数用户转向OpenSSL,因为他们希望配置和运行支持SSL的web服务器。该过程包括三个步骤:
本节将介绍这些步骤(以及其他几个步骤)。
准备运行TLS服务器的第一步是生成私钥。在开始之前,您必须做出几个决定:
密钥算法
OpenSSL支持RSA、DSA、ECDSA和EdDSA密钥算法,但并非所有这些算法在实践中都有用。例如,DSA已经过时,EdDSA尚未得到广泛支持。这就让我们在证书中使用RSA和ECDSA算法。
【目前OpenSSH支持ed25519】
密钥大小
默认密钥大小可能不安全,这就是为什么您应该始终显式配置密钥大小。例如,RSA密钥的默认值曾经是512位,这是不安全的。如果你今天在服务器上使用了512位密钥,入侵者可能会拿走你的证书,并使用暴力手段恢复你的私钥,之后她可以冒充你的网站。如今,2048位RSA密钥被认为是安全的,而ECDSA则是256位。
密码短语
使用带有密钥的密码是可选的,但强烈建议使用。受保护的密钥可以安全地存储、运输和备份。另一方面,这样的密钥很不方便,因为没有密码就无法使用。例如,每次您想重新启动web服务器时,可能会要求您输入密码。对大多数人来说,这要么太不方便,要么对可用性有不可接受的影响。此外,在生产中使用受保护的密钥实际上并没有显著提高安全性。这是因为,一旦激活,私钥在程序内存中就不受保护;能够访问服务器的攻击者只需稍加努力即可从那里获得密钥。因此,当私钥未安装在生产系统上时,密码短语应仅被视为一种保护私钥的机制。换句话说,在生产系统上,将密码放在密钥旁边是可以的。如果您需要更好的生产安全性,您应该投资硬件解决方案。
要生成RSA密钥,请使用以下 genpkey
命令:
xxxxxxxxxx
$ openssl genpkey -out fd.key \
-algorithm RSA \
-pkeyopt rsa_keygen_bits:2048 \
-aes-128-cbc
..........................................+++++
..............................................................+++++
Enter PEM pass phrase: ************
Verifying - Enter PEM pass phrase: ************
在这里,我指定使用AES-128保护密钥。您也可以使用AES-256(使用 -aes-256-cbc
开关),但最好远离其他算法(例如 DES、3DES和SEED)。
-out filename
将密钥输出到指定文件。如果未指定此参数,则使用标准输出。
-algorithm alg
使用的公钥算法,如RSA、DSA、DH或DHX。如果使用此选项,则必须位于任何 -pkeyopt
选项之前。选项 -paramfile
和 -algorithm
是互斥的。除了标准的内置算法外,引擎还可以添加算法。
私钥生成的有效内置算法名称为RSA、RSA-PSS、EC、X25519、X448、ED25519和ED448。
用于参数生成的有效内置算法名称(请参阅 -genparam
选项)为DH、DSA和EC。
请注意,算法名称X9.42 DH可以用作DHX密钥的同义词,PKCS#3是指DH密钥。DH和DHX密钥之间不共享某些选项。
-pkeyopt opt:value
将公钥算法选项 opt
设置为 value
。支持的精确选项集取决于所使用的公钥算法及其实现。有关更多详细信息,请参阅下面的“密钥生成选项”和“参数生成选项”。
-aes-128-cbc
xxxxxxxxxx
警告
默认情况下,OpenSSL会将新RSA密钥的公共指数设置为65537。这就是所谓的短公共指数(short public exponent),它显著提高了RSA验证的性能。你可能会遇到建议,选择3作为你的公开指数,使验证更快。虽然这是真的,但使用3作为公共指数有一些令人不快的历史弱点,这就是为什么你应该坚持使用65537。这种选择提供了一个在过去被证明是有效的安全裕度。
当您使用 genpkey
命令时,生成的私钥以PKCS#8格式存储,该格式只是文本,看起来不太像:
xxxxxxxxxx
$ cat fd.key
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQInW7GrFjUhUcCAggA
MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBn8AErtRKB9p7ii1+g2OhWBIIE
0MnC2dwGznZqpTMX0MYekzyxe4dKlJiIsVr1hgwmjFifzEBs/KvHBV3eIe9wDAzq
[21 lines removed...]
IfveVZzM6PLbDaysxX6jEgi4xVbqWugd9h3eAPeBv9Z5iZ/bZq5hMbt37ElA2Rnh
RfmWSzlASjQi4XAHVLCs6XmULCda6QGvyB7WXxuzbhOv3C6BPXR49z6S1MFvOyDA
2oaXkfS+Ip3x2svgFJj/VpYZHUHwRCzXcDl/CdVg9fxwxcYHuJDH16Qfue/LRtiJ
hqr4fHrnbbk+MZpDaU+h4shLRBg2dONdUEzhPkpdOOkF
-----END ENCRYPTED PRIVATE KEY-----
然而,私钥不仅仅是一团随机数据,即使它看起来是这样的。您可以使用以下 rsa
命令查看密钥的结构:
xxxxxxxxxx
$ openssl pkey -in fd.key -text -noout
Enter pass phrase for fd.key: ****************
RSA Private-Key: (2048 bit, 2 primes)
modulus:
00:be:79:08:22:1a:bc:78:3c:17:34:4a:d3:5f:2b:
[...]
publicExponent: 65537 (0x10001)
privateExponent:
10:20:95:54:b5:e8:d1:51:5d:31:9b:48:4c:5d:90:
[...]
prime1:
00:f5:3f:74:cf:ef:8f:93:e9:54:b3:79:a1:f2:91:
5a:7e:15:13:26:f7:f9:d7:a8:f3:f9:6b:2b:90:93:
57:54:cc:84:c9:ea:6f:9f:39:ad:ad:60:4c:f0:68:
16:db:1a:49:51:56:87:f1:70:ae:c9:42:89:2a:38:
55:3e:17:a0:78:a7:52:49:10:79:cf:99:ae:53:c8:
e0:60:5d:7e:91:26:86:3b:79:d2:70:c0:39:38:dd:
ed:ee:75:c0:15:c6:30:51:00:a8:93:f3:8b:25:01:
04:25:72:fc:9c:e9:73:d0:93:11:2d:82:e2:e3:d0:
66:c0:36:2f:b6:de:de:0d:47
prime2:
00:c6:d2:ce:66:b5:35:6b:35:d7:bb:b0:e3:f4:2d:
[...]
exponent1:
00:e9:2e:e9:b9:5f:f5:2b:54:fa:c5:1f:4c:7d:5f:
[...]
exponent2:
00:83:ea:bc:ad:a2:cf:a5:a9:9c:d0:d8:85:f6:ae:
[...]
coefficient:
68:18:a7:4f:aa:86:a7:e0:92:49:76:8d:24:65:fa:
[...]
如果你只需要单独拥有密钥的公共部分,你可以使用以下rsa命令:
xxxxxxxxxx
$ openssl pkey -in fd.key -pubout -out fd-public.key
Enter pass phrase for fd.key: ****************
如果你查看新生成的文件,你会看到标记清楚地表明所包含的信息确实是公开的:
xxxxxxxxxx
$ cat fd-public.key
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvnkIIhq8eDwXNErTXytD
U1JGrYUgFsN8IgFVMJmAuY15dBvSCO+6y9FA0H08utJVtHScyWeOlo1uo0TQ3RWr
Pe7W3O2SaW2gIby2cwzGf/FBExZ+BCNXkN5z8Kd38PXDLt8ar+7MJ3vrb/sW7zs2
v+rtfRar2RmhDPpVvI6sugCeHrvYDGdA/gIZAMMg3pVFivPpHnTH4AR7rTzWCWlb
nCB3z2FVYpvumrY8TvIo5OioD2I+TQyvlxDRo14QWxIdZxvPcCUxXMN9MC8fBtLu
IlllDmah8JzF2CF5IxVgVhi7hyTtSQfKsK91tAvN30F9qkZNEpjNX37M5duHUVPb
tQIDAQAB
-----END PUBLIC KEY-----
验证输出是否包含您所期望的内容是很好的做法。例如,如果您忘记在命令行中包含 -pubout
开关,则输出将包含您的私钥而不是公钥。
该过程与ECDSA密钥类似,只是不可能创建任意大小的密钥。相反,对于每个密钥,您可以选择一条命名曲线(named curve),该曲线控制密钥点大小,但也控制其他EC参数。以下示例使用P-256(或secp256r1)命名的曲线创建256位ECDSA密钥:
xxxxxxxxxx
$ openssl genpkey -out fd.key \
-algorithm EC \
-pkeyopt ec_paramgen_curve:P-256 \
-aes-128-cbc
Enter PEM pass phrase: ****************
Verifying - Enter PEM pass phrase: ****************
OpenSSL支持许多命名曲线,但对于web服务器密钥,通常(仍然)仅限于广泛支持的两条曲线:P-256(也称为secp256r1或prime256v1)和P-384(secp384r1)。在这两者中,P-256足够安全,性能更好。如果您想查看OpenSSL支持的所有命名曲线的列表,可以使用 ecparam
命令和 -list_curves
开关来获取。
也支持最近添加的x25519、x448、ed25519和ed448,但它们是不同类型的曲线,必须使用 -algorithm
开关指定,例如:
xxxxxxxxxx
$ openssl genpkey -algorithm ed25519
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIF6K3m4WM7/yMA9COn6HYyx7PjJCIzY7bnBoKupYgdTL
-----END PRIVATE KEY-----
一旦你有了私钥,你就可以继续创建证书签名请求(Certificate Signing Request,CSR)。这是一个要求CA签署证书的正式请求,它包含请求证书的实体的公钥和有关该实体的一些信息。这些数据都将成为证书的一部分。CSR总是使用与其携带的公钥相对应的私钥进行签名。
CSR创建通常是一个交互式过程,在此过程中,您将提供证书可分辨名称的元素。仔细阅读openssl工具给出的说明;如果你想让一个字段为空,你必须在行上输入一个点(.),而不是直接点击 Return 。如果执行后者,OpenSSL将用默认值填充相应的CSR字段。(当与默认的OpenSSL配置一起使用时,这种行为没有任何意义,这几乎是每个人都会做的。一旦你意识到你实际上可以通过修改OpenSSL配置或提供自己的配置文件来更改默认值,这种行为就有意义了。)
xxxxxxxxxx
$ openssl req -new -key fd.key -out fd.csr
Enter pass phrase for fd.key: ****************
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:GB
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:London
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Feisty Duck Ltd
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:www.feistyduck.com
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
xxxxxxxxxx
笔记
根据RFC 2985第5.4.1节,质询密码(challenge password)是一个可选字段,用于在证书吊销期间识别请求证书的原始实体。如果输入,密码将逐字包含在CSR中并传达给CA。很少有CA依赖于此字段;我看到的所有说明都建议不要碰它。拥有挑战密码不会以任何方式提高CSR的安全性。此外,此字段不应与密钥短语混淆,密钥短语是一个单独的功能。
生成CSR后,使用它来签署您自己的证书和/或将其发送到公共CA并要求其签署证书。以下章节将介绍这两种方法。但在你这样做之前,最好仔细检查CSR是否正确。方法如下:
xxxxxxxxxx
$ openssl req -text -in fd.csr -noout
Certificate Request:
Data:
Version: 1 (0x0)
Subject: C = GB, L = London, O = Feisty Duck Ltd, CN = www.feistyduck.com
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:8a:d5:de:69:30:c7:77:b0:a0:54:f7:b3:34:9a:
96:1c:23:81:e3:9c:0c:81:a6:8a:a5:14:76:f4:4c:
b3:10:cb:ee:50:d1:ea:70:e9:7f:8f:75:67:f9:12:
83:b0:11:e7:6c:64:de:bc:af:bd:3f:43:da:b8:41:
96:75:34:63:85
ASN1 OID: prime256v1
NIST CURVE: P-256
Attributes:
a0:00
Signature Algorithm: ecdsa-with-SHA256
30:44:02:20:52:b9:cf:ca:d1:25:1c:b7:57:65:fb:24:5d:95:
15:f0:39:79:36:6c:d6:0a:42:6e:26:7c:54:e8:71:17:a5:99:
02:20:5a:e0:cd:b3:60:ec:2c:fc:29:8c:f9:21:01:08:9a:a3:
0d:fc:9a:d3:4f:24:fb:23:4f:c6:d7:a2:14:d1:54:f9
如果您要续订证书并且不想对其中显示的信息进行任何更改,则可以节省一些打字时间。使用以下命令,您可以从现有证书创建一个全新的CSR:
xxxxxxxxxx
$ openssl x509 -x509toreq -in fd.crt -out fd.csr -signkey fd.key
xxxxxxxxxx
笔记
除非您正在使用某种形式的公钥固定并希望继续使用现有密钥,否则最佳做法是每次申请新证书时都生成一个新密钥。密钥生成快速且廉价,并在未被发现的泄露情况下减少您的暴露。
CSR生成不一定是交互式的。使用自定义OpenSSL配置文件,您既可以自动执行该过程(如本节所述),也可以执行某些无法交互的操作(例如,如何在同一证书中包含多个域名,如后续章节所述)。
例如,假设我们想为www.feistyduck.com自动生成CSR。我们将首先创建一个包含以下内容的文件 fd.cnf :
x[req]
prompt = no
distinguished_name = dn
req_extensions = ext
input_password = PASSPHRASE
[dn]
CN = www.feistyduck.com
emailAddress = webmaster@feistyduck.com
O = Feisty Duck Ltd
L = London
C = GB
[ext]
subjectAltName = DNS:www.feistyduck.com,DNS:feistyduck.com
现在,您可以直接从命令行创建CSR:
xxxxxxxxxx
$ openssl req -new -config fd.cnf -key fd.key -out fd.csr
如果你正在配置TLS服务器供自己使用或进行快速测试,有时你不想去CA获取公开可信的证书。使用自签名证书要容易得多。
如果您已经拥有CSR,请使用以下命令创建证书:
xxxxxxxxxx
$ openssl x509 -req -days 365 -in fd.csr -signkey fd.key -out fd.crt
Signature ok
subject=C = GB, L = London, O = Feisty Duck Ltd, CN = www.feistyduck.com
Getting Private key
Enter pass phrase for fd.key: ****************
实际上,您不必在单独的步骤中创建CSR。以下命令创建仅以密钥开头的自签名证书:
xxxxxxxxxx
$ openssl req -new -x509 -days 365 -key fd.key -out fd.crt
如果您不希望被问到任何问题,请使用 -subj
开关在命令行上提供证书使用者信息:
xxxxxxxxxx
$ openssl req -new -x509 -days 365 -key fd.key -out fd.crt \
-subj "/C=GB/L=London/O=Feisty Duck Ltd/CN=www.feistyduck.com"
默认情况下,OpenSSL生成的证书只有一个通用名称,并且只对一个主机名有效。因此,即使您有相关的网站,您也必须为每个网站使用单独的证书。在这种情况下,使用单个多域证书更有意义。此外,即使您运行的是单个网站,您也需要确保证书对最终用户访问它的所有可能路径都有效。在实践中,这意味着至少使用两个名称,一个带有www前缀,一个没有(例如www.feistyduck.com和feistydack.com)。
有两种机制可以在证书中支持多个主机名。第一种方法是使用名为主题备选名称(SAN)的X.509扩展列出所有所需的主机名。第二种是使用通配符。如果更方便,您也可以将这两种方法结合使用。在实践中,对于大多数网站,您可以指定一个裸域名和一个通配符来覆盖所有子域(例如,feistyduck.com和*.feisyduck.com)。
xxxxxxxxxx
警告
当证书包含替代名称时,所有通用名称都将被忽略。CA生成的较新证书甚至可能不包括任何通用名称。因此,在备选名称列表中包括所有所需的主机名。
首先,将扩展信息放在单独的文本文件中。我将其命名为 fd.ext 。在文件中,指定扩展名(subjectAltName)的名称,并列出所需的主机名,如下例所示:
xxxxxxxxxx
subjectAltName = DNS:*.feistyduck.com, DNS:feistyduck.com
然后,当使用x509命令颁发证书时,请使用 -extfile
开关参考该文件:
xxxxxxxxxx
$ openssl x509 -req -days 365 \
-in fd.csr -signkey fd.key -out fd.crt \
-extfile fd.ext
剩下的过程与以前没有什么不同。但是,当您稍后检查生成的证书时(请参阅下一节),您会发现它包含SAN扩展:
xxxxxxxxxx
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:*.feistyduck.com, DNS:feistyduck.com
【《TLS Mastery》第三章中明确反对使用通配符证书,风险来自多台主机更容易面临私钥丢失的可能。】
证书在文本编辑器中看起来不太像,但它们包含大量信息;您只需要知道如何解压缩它。x509命令就可以做到这一点,所以让我们用它来查看您生成的自签名证书。
在以下示例中,我使用 -text
开关打印证书内容,使用 -noout
通过不打印编码的证书本身来减少混乱(这是默认行为):
xxxxxxxxxx
$ openssl x509 -text -in fd.crt -noout
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
76:bc:fb:f6:06:0e:61:eb:99:5e:83:ea:ef:92:0b:32:4f:fd:3b:51
Signature Algorithm: ecdsa-with-SHA256
Issuer: C = GB, L = London, O = Feisty Duck Ltd, CN = www.feistyduck.com
Validity
Not Before: Aug 15 09:31:54 2020 GMT
Not After : Aug 15 09:31:54 2021 GMT
Subject: C = GB, L = London, O = Feisty Duck Ltd, CN = www.feistyduck.com
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:8a:d5:de:69:30:c7:77:b0:a0:54:f7:b3:34:9a:
96:1c:23:81:e3:9c:0c:81:a6:8a:a5:14:76:f4:4c:
b3:10:cb:ee:50:d1:ea:70:e9:7f:8f:75:67:f9:12:
83:b0:11:e7:6c:64:de:bc:af:bd:3f:43:da:b8:41:
96:75:34:63:85
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:*.feistyduck.com, DNS:feistyduck.com
Signature Algorithm: ecdsa-with-SHA256
30:45:02:20:4d:36:34:cd:e9:3e:df:18:52:e7:74:c4:a1:97:
91:6a:e7:c1:6d:12:01:63:d1:fd:90:28:32:70:24:5c:be:35:
02:21:00:bd:02:64:c9:8b:27:8f:79:c7:a4:41:7c:31:2f:98:
29:3e:db:8c:f3:f1:d7:bb:fa:fe:95:48:be:16:e1:ab:1b
自签名证书通常只包含最基本的证书数据,其中大部分是不言自明的。本质上,证书的主体是添加签名的。相比之下,公共CA颁发的证书更有趣,因为它们包含许多附加字段(通过X.509扩展机制)。
当您查看公共证书时,在输出的第一部分中,您将找到与自签名证书中类似的信息。事实上,唯一的区别是,如颁发者信息所示,此证书具有不同的父级。
xxxxxxxxxx
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
03:5e:50:53:75:08:1a:f2:7d:27:64:4f:d5:6f:1a:02:07:89
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
Validity
Not Before: Aug 2 23:10:45 2020 GMT
Not After : Oct 31 23:10:45 2020 GMT
Subject: CN = www.feistyduck.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:c8:14:4f:33:9a:db:bb:e7:e3:78:93:46:5d:56:
a7:bc:58:86:43:dc:ea:c1:01:52:4b:0f:20:b7:38:
[...]
Exponent: 65537 (0x10001)
X509v3 extensions:
[...]
主要区别在于X.509扩展,它包含了大量非常有趣的信息。让我们检查一下扩展中有什么,以及为什么它在那里,没有特别的顺序。
基本约束(Basic Constraints)扩展用于将证书标记为属于CA,使其能够签署其他证书。非CA证书将省略此扩展名,或者将CA的值设置为 FALSE
。此扩展至关重要,这意味着所有使用证书的软件都必须理解其含义。
xxxxxxxxxx
X509v3 Basic Constraints: critical
CA:FALSE
密钥使用(Key Usage,KU)和 扩展密钥使用(Extended Key Usage,EKU)扩展限制了证书的用途。如果存在这些扩展,则只允许使用列出的用途。如果扩展不存在,则没有使用限制。您在这个例子中看到的是典型的web服务器证书,例如,它不允许代码签名:
xxxxxxxxxx
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
CRL分发点(CRL Distribution Points)扩展列出了可以找到CA证书吊销列表(Certificate Revocation List,CRL)信息的地址。在需要吊销证书的情况下,此信息很重要。CRL是CA签名的吊销证书列表,按固定时间间隔(例如七天)发布。Let's Encrypt不提供CRL,因此我从另一个证书中获取了以下代码片段:
xxxxxxxxxx
X509v3 CRL Distribution Points:
Full Name:
URI:http://crl.starfieldtech.com/sfs3-20.crl
xxxxxxxxxx
笔记
您可能已经注意到CRL位置没有使用安全服务器,您可能想知道链接是否因此不安全。事实并非如此。因为每个CRL都由颁发它的CA签名,所以客户端能够验证其完整性。事实上,如果CRL是通过TLS分发的,浏览器可能会面临鸡和蛋的问题,他们想验证传递CRL的服务器本身使用的证书的吊销状态!
证书策略(Certificate Policies)扩展用于指示颁发证书所依据的策略。例如,您可以在这里找到用于确定所有者身份的验证类型的指示。可以找到扩展验证(Extended Validation,EV)指标(如下例所示)。这些指示器采用唯一对象标识符(Object Identifiers,OIDs)的形式,其中一些是通用的,一些是特定于颁发CA的。在以下示例中, OID 2.23.140.1.2.1
表示域验证的证书。此外,此扩展通常包含一个或多个 证书策略声明(Certificate Policy Statement ,CPS)点,这些点通常是网页或PDF文档。
xxxxxxxxxx
X509v3 Certificate Policies:
Policy: 2.23.140.1.2.1
Policy: 1.3.6.1.4.1.44947.1.1.1
CPS: http://cps.letsencrypt.org
权威信息访问(Authority Information Access,AIA)扩展通常包含两条重要信息。首先,它列出了CA的在线证书状态协议(Online Certificate Status Protocol,OCSP)响应者的地址,可用于实时检查证书吊销情况。扩展还可能包含一个链接,指向可以找到颁发者证书(链中的下一个证书)的位置。如今,服务器证书很少由受信任的根证书直接签名,这意味着用户必须在其配置中包含一个或多个中间证书。错误很容易犯,会使证书失效。一些客户端将使用此扩展中提供的信息来修复不完整的证书链,但许多客户端不会。
xxxxxxxxxx
Authority Information Access:
OCSP - URI:http://ocsp.int-x3.letsencrypt.org
CA Issuers - URI:http://cert.int-x3.letsencrypt.org/
主题密钥标识符(Subject Key Identifier)和权威密钥标识符(Authority Key Identifier)扩展分别建立唯一的主题和权威密钥标识。证书的授权密钥标识符扩展中指定的值必须与颁发证书中的主题密钥标识符扩展指定的值匹配。此信息在证书路径构建过程中非常有用,在该过程中,客户端试图找到从叶子(服务器)证书到受信任根的所有可能路径。证书颁发机构通常会使用一个私钥和多个证书,此字段允许软件可靠地识别哪个证书可以与哪个密钥匹配。在现实世界中,服务器提供的许多证书链都是无效的,但这一事实往往被忽视,因为浏览器能够找到替代的信任路径。
xxxxxxxxxx
X509v3 Subject Key Identifier:
A1:EC:11:C6:E1:E8:F7:E6:98:85:FA:9A:53:F8:B8:F1:D6:88:F9:A3
X509v3 Authority Key Identifier:
keyid:A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1
主题可选名称(Subject Alternative Name)扩展用于列出证书有效的所有主机名。这个扩展曾经是可选的;如果它不存在,客户端将转而使用通用名称(common name,CN)中提供的信息,该名称是主题字段的一部分。如果存在扩展名,则在验证过程中忽略CN字段的内容。
xxxxxxxxxx
X509v3 Subject Alternative Name:
DNS:www.feistyduck.com, DNS:feistyduck.com
最后,最新添加的是证书透明性(Certificate Transparency,CT)扩展,用于将日志记录证明携带到各种公共CT日志中。根据证书的生命周期,您可能会看到两到五个签名证书时间戳(Signed Certificate Timestamps,SCTs)。对于识别证书有效性所必需的SCT的数量和类型,没有一套统一的要求。从技术上讲,每个客户都有权指定他们的期望。在实践中,Chrome是第一个需要CT的浏览器,其他客户端可能会效仿它。
xxxxxxxxxx
CT Precertificate SCTs:
Signed Certificate Timestamp:
Version : v1 (0x0)
Log ID : 5E:A7:73:F9:DF:56:C0:E7:B5:36:48:7D:D0:49:E0:32:
7A:91:9A:0C:84:A1:12:12:84:18:75:96:81:71:45:58
Timestamp : Aug 3 00:10:45.300 2020 GMT
Extensions: none
Signature : ecdsa-with-SHA256
30:45:02:21:00:BB:7F:D0:E1:E6:CD:4B:E7:79:30:AE:
BE:F6:50:4F:36:A4:F6:1D:65:21:1A:05:A9:B3:F0:53:
BA:FA:AC:6D:FB:02:20:52:23:B9:F9:B6:73:34:7F:3D:
7F:42:5C:E3:9D:3D:DA:D8:7F:B3:7E:21:0C:27:54:9B:
DA:E1:3F:0F:8E:09:60
[...]
私钥和证书可以以多种格式存储,这意味着您通常需要将它们从一种格式转换为另一种格式。最常见的格式是:
Binary(DER)证书
包含使用DER ASN.1编码的原始形式的X.509证书。
ASCII(PEM)证书
包含一个base64编码的DER证书,其中 -----BEGIN certificate-----
用作页眉,-----END certificate------
用作页脚。通常每个文件只有一个证书,尽管有些程序根据上下文允许多个证书。例如,较旧的Apache web服务器版本要求服务器证书单独保存在一个文件中,所有中间证书一起保存在另一个文件。
传统OpenSSL密钥格式
包含使用DER ASN.1编码的原始形式的私钥。从历史上看,OpenSSL使用基于PKCS#1的格式。如今,如果您使用正确的命令(即 genpkey
),OpenSSL默认为PKCS #8。
ASCII (PEM)密钥
包含一个base64编码的DER密钥,有时还带有额外的元数据(例如,用于密码保护的算法)。页眉和页脚中的文本可能不同,具体取决于所使用的基础键格式。
PKCS #7 证书
一种为传输签名或加密数据而设计的复杂格式,在RFC 2315中定义。它通常带有 .p7b 和 .p7c 扩展名,可以根据需要包含整个证书链。Java的 keytool
实用程序支持这种格式。
PKCS #8 密钥
私钥存储的新默认格式。PKCS #8在RFC 5208中定义。如果出于任何原因需要从PKCS #8转换为旧格式,请使用 pkcs8
命令。
PKCS #12(PFX)密钥和证书
一种复杂的格式,可以存储和保护服务器密钥以及整个证书链。它通常以 .p12 和 .pfx 扩展名出现。此格式通常用于Microsoft产品,但也用于客户端证书。如今,PFX名称被用作PKCS #12的同义词,尽管PFX在很久以前就提到了不同的格式(PKCS#12的早期版本)。你不太可能在任何地方遇到旧版本。
PEM和DER格式之间的证书转换是使用x509工具执行的。要将证书从PEM转换为DER格式:
xxxxxxxxxx
$ openssl x509 -inform PEM -in fd.pem -outform DER -out fd.der
要将证书从DER格式转换为PEM格式:
xxxxxxxxxx
$ openssl x509 -inform DER -in fd.der -outform PEM -out fd.pem
如果需要在DER和PEM格式之间转换私钥,语法是相同的,但使用了不同的命令: RSA密钥使用rsa
,DSA密钥使用 dsa
。如果您正在处理新的PKCS #8格式,请使用 pkey
工具。
将PEM格式的密钥和证书转换为PKCS#12只需一个命令。以下示例将密钥(fd.key)、证书(fd.crt)和中间证书(fd-chain.crt)转换为等效的单个PKCS#12文件:
xxxxxxxxxx
$ openssl pkcs12 -export \
-name "My Certificate" \
-out fd.p12 \
-inkey fd.key \
-in fd.crt \
-certfile fd-chain.crt
Enter Export Password: ****************
Verifying - Enter Export Password: ****************
反向转换并不那么简单。您可以使用单个命令,但在这种情况下,您将在一个文件中获得全部内容:
xxxxxxxxxx
$ openssl pkcs12 -in fd.p12 -out fd.pem -nodes
现在,您必须在您最喜欢的编辑器中打开文件 fd.pem ,并手动将其拆分为单独的密钥、证书和中间证书文件。当你这样做的时候,你会注意到在每个组件之前提供了额外的内容。例如:
xxxxxxxxxx
Bag Attributes
localKeyID: E3 11 E4 F1 2C ED 11 66 41 1B B8 83 35 D2 DD 07 FC DE 28 76
subject=/1.3.6.1.4.1.311.60.2.1.3=GB/2.5.4.15=Private Organization/serialNumber=06694169/C=GB/ST=London/L=London/O=Feisty Duck Ltd /CN=www.feistyduck.com
issuer=/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./OU=http://certificates.starfieldtech.com/repository/CN=Starfield Secure Certification Authority
-----BEGIN CERTIFICATE-----
MIIF5zCCBM+gAwIBAgIHBG9JXlv9vTANBgkqhkiG9w0BAQUFADCB3DELMAkGA1UE
BhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAj
[...]
这个额外的元数据对于快速识别证书非常方便。显然,您应该确保主证书文件包含叶子服务器证书,而不是其他证书。此外,您还应该确保以正确的顺序提供中间证书,签发证书在签名证书之后。如果你看到一个自签名的根证书,可以随意删除或将其存储在其他地方;它不应该进入链条。
xxxxxxxxxx
警告
最终的转换输出不应包含除编码密钥和证书之外的任何内容。虽然有些工具足够聪明,可以忽略不需要的东西,但其他工具则不然。在PEM文件中留下额外数据可能会导致难以排除的问题。
可以让OpenSSL为您拆分组件,但这样做需要多次调用pkcs12命令(包括每次键入捆绑包密码):
xxxxxxxxxx
$ openssl pkcs12 -in fd.p12 -nocerts -out fd.key -nodes
$ openssl pkcs12 -in fd.p12 -nokeys -clcerts -out fd.crt
$ openssl pkcs12 -in fd.p12 -nokeys -cacerts -out fd-chain.crt
这种方法不会为您节省太多工作。您仍然必须检查每个文件,以确保它包含正确的内容并删除元数据。
要从PEM转换为PKCS#7,请使用 crl2pkcs7
命令:
xxxxxxxxxx
$ openssl crl2pkcs7 -nocrl -out fd.p7b -certfile fd.crt -certfile fd-chain.crt
要从PKCS#7转换为PEM,请使用带有-print_certs开关的pkcs7命令:
xxxxxxxxxx
openssl pkcs7 -in fd.p7b -print_certs -out fd.pem
与 PKCS#12
的转换类似,您现在必须编辑 fd.pem 文件进行清理,并将其拆分为所需的组件。
TLS服务器配置中的一个常见任务是选择要使用的密码套件。为了安全地通信,TLS需要决定使用哪些加密原语来实现其目标(例如机密性)。这是通过选择合适的密码套件来实现的,该套件会对如何进行身份验证、密钥交换、加密和其他操作做出一系列决定。依赖OpenSSL的程序通常采用与OpenSSL相同的套件配置方法,只需传递配置选项即可。
在TLS 1.3之前,通常的服务器配置将包括密码套件配置和服务器在协商过程中首选更强套件的选项。由于TLS 1.3的设计与早期协议版本存在一些差异,OpenSSL决定对其进行不同的配置,从而增加了服务器配置的复杂性。我将在以下部分讨论这个问题。
制定一个好的套件配置可能会非常耗时,而且有很多细节需要考虑。我写这一节是为了实现两个目标。如果你不想花很多时间学习如何使用OpenSSL以及如何对密码套件进行排名,只需使用我提供的默认配置即可。另一方面,如果您想了解OpenSSL配置的来龙去脉,本节将提供答案。
让我们从确定OpenSSL安装支持哪些套件开始深入研究。为此,使用 -v
开关和 ALL:COMPLEMENTOFALL
作为参数调用密码命令:
xxxxxxxxxx
$ openssl ciphers -v 'ALL:COMPLEMENTOFALL'
xxxxxxxxxx
小贴士
从OpenSSL 1.0.0开始,密码命令支持大写-V开关以提供额外的详细输出。在这种模式下,输出还将包含套件ID,这些ID总是很方便。例如,OpenSSL并不总是对套件使用RFC名称;在这种情况下,您必须使用ID进行交叉检查。在本节中,我使用小写-v,因为输出更容易在书中显示。
此时,您将看到很多输出,包括您安装的OpenSSL所提供的一切。就我而言,输出中有162套。让我们来看一行:
xxxxxxxxxx
TLS_AES_256_GCM_SHA384 TLSv1.3 Kx=any Au=any Enc=AESGCM(256) Mac=AEAD
每一行输出都提供了一个套件的扩展信息。从左到右:
套件名称
所需的最小协议版本
列表中的一些套件在协议列中显示SSLv3。这没什么好担心的。这只意味着该套件与这个旧的(和过时的)协议版本兼容。如果使用这些套件,您的配置将不会降级到SSL 3.0。
密钥交换算法
认证算法
加密算法和强度(strength)
MAC(完整性)算法
传统上,OpenSSL不使用官方套件名称,尽管现在TLS 1.3套件使用了官方套件名称。最近,当您将 -stdname
开关添加到密码工具中时,您将同时获得officia套件名称和OpenSSL名称。
xxxxxxxxxx
备注
您可能会注意到,所有TLS 1.3套件都有任何密钥下交换和身份验证。这是因为该协议版本将握手的这两个方面从密码套件中移出,并放入协议本身。它还删除了所有不安全的算法,因此在这种情况下,任何算法都不是坏的或不安全的。
【另,FreeBSD14是142个,Debian是158个】
在上一节中,我们讨论了如何获得支持套件的完整列表,但该列表具有欺骗性。仅仅因为支持某些内容并不意味着它将被启用。除非您另有说明,否则密码命令甚至会输出不允许的套件。诀窍是使用 -s
开关,之后套件数量将从162个减少到只有77个。
【FreeBSD是64个,Debian是74个】
当然,这一减少在很大程度上是由于删除了PSK和SRP套件,删除了66个条目(降至96个)。剩下的21个条目的差异是由于OpenSSL现在使用的安全级别概念造成的。
密码套件配置很复杂,大多数人都不是密码学专家。例如,很容易遵循互联网上一些过时的建议,在配置中添加不安全的元素。此外,有些安全方面无法通过密码套件配置,以前根本无法控制。因此,安全级别旨在保证最低安全要求,即使请求了不正确的配置。它们是一个非常有用的安全网。
OpenSSL安全级别表:
安全级别 | 含义 |
---|---|
Level 0 | 没有限制。允许在编译时启用所有功能。不安全的。 |
Level 1 | 安全级别对应于最低80位的安全性。弱。 |
Level 2 | 安全级别设置为112位安全级别。 |
Level 3 | 安全级别设置为128位安全级别。 |
Level 4 | 安全级别设置为192位安全级别。 |
Level 5 | 安全级别设置为256位安全级别。 |
安全级别最重要的方面是知道不使用什么,这是前两个级别。级别0没有限制,并且可能不安全(取决于在编译时启用了哪些功能)。在实践中,你不太可能需要这个级别。级别1稍好一些,但仍然允许弱元素。出于与遗留系统的互操作性目的,您可能需要此级别。级别1是OpenSSL中的默认安全级别。
在实践中,你应该以2级为基准。此级别支持2048位RSA密钥,目前大多数网站都在使用。不允许使用SSL 2和SSL 3等弱协议,以及RC4和SHA1。如果你没有使用OpenSSL,你可能会发现你的发行版已经为你做出了这个选择;例如,Ubuntu 20.04 LTS选择级别2作为默认值。
如果您有特定的安全要求并希望实施更强大的加密,则应考虑级别3及以上。例如,如果启用级别3,则不允许使用2048位RSA密钥。因为比这更强的密钥非常慢,所以这种安全级别的选择隐式地将您限制为ECDSA密钥。
通过在套件配置中使用 -s
开关和 @SECLEVEL
关键字,您可以更好地了解安全级别。例如,让我们看看从级别3切换到级别4如何影响一个任意的套件配置。在级别3,输出中有四个套件:
xxxxxxxxxx
$ openssl ciphers -v -s -tls1_2 'EECDH+AESGCM @SECLEVEL=3'
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(128) Mac=AEAD
ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD
然而,在级别4,输出中只有两个套件,因为删除了128位套件:
xxxxxxxxxx
$ openssl ciphers -v -s -tls1_2 'EECDH+AESGCM @SECLEVEL=4'
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(256) Mac=AEAD
您可能已经注意到,前面的示例引入了 -tls1_2
开关,它只输出可以与TLS 1.2协商的套件。当您只对一种协议感兴趣时,此开关以及 -tls1_3
、-tls1_1
、-tls1
和 -ssl3
对于删除不需要的输出非常有用。
如果您正在使用密码工具,并且不熟悉TLS 1.3的配置方式(例如,您只使用不支持此协议的OpenSSL版本),您可能会对以下事实感到困惑:无论您指定什么配置,TLS 1.3套件总是列在顶部。这是因为OpenSSL为TLS 1.3套件配置引入了一种单独的机制。在库级别,有单独的函数调用,并且有单独的方法与命令行工具一起使用。
当涉及到密码工具时,要控制TLS 1.3套件,您需要使用 -ciphersuites
密码套件开关。为了说明这一点,让我们启用一个TLS 1.3套件和一个SEED套件:
xxxxxxxxxx
$ openssl ciphers -v -s -ciphersuites TLS_AES_256_GCM_SHA384 SEED-SHA
TLS_AES_256_GCM_SHA384 TLSv1.3 Kx=any Au=any Enc=AESGCM(256) Mac=AEAD
SEED-SHA SSLv3 Kx=RSA Au=RSA Enc=SEED(128) Mac=SHA1
当他们为TLS 1.3添加这种新的配置机制时,OpenSSL开发人员抓住机会通过删除各种工具和关键字来简化套件的配置,这些工具和关键字现在可以称为遗留套件配置。TLS 1.3唯一支持的方法是按照您希望支持的顺序提供一个冒号分隔的套件列表。仅此而已。例如:
xxxxxxxxxx
$ openssl ciphers -v -s -tls1_3 \
-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384
TLS_AES_128_GCM_SHA256 TLSv1.3 Kx=any Au=any Enc=AESGCM(128) Mac=AEAD
TLS_AES_256_GCM_SHA384 TLSv1.3 Kx=any Au=any Enc=AESGCM(256) Mac=AEAD
xxxxxxxxxx
备注
尽管TLS 1.3套件有一个单独的配置字符串,但配置仍然受到旧配置字符串中指定的安全级别配置的影响。
这种TLS 1.3配置的新方法对现实生活有何影响?根据您的工具,您现在可能会发现自己需要使用两个配置字符串,而以前只有一个。在Apache web服务器中,SSLCipherSuite
指令已通过可选的第一个参数进行了扩展,使您能够针对要配置的协议。所以你可以这样做:
xxxxxxxxxx
SSLCipherSuite TLSv1.3 TLS_AES_128_GCM_SHA256
SSLCipherSuite EECDH+AES128+AESGCM
结果将相当于以下内容:
xxxxxxxxxx
TLS_AES_128_GCM_SHA256 TLSv1.3 Kx=any Au=any Enc=AESGCM(128) Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(128) Mac=AEAD
ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD
并非所有工具都添加了对TLS 1.3套件配置的支持。相反,您总是得到OpenSSL默认值。对于大多数用户来说,这还不是一个真正的问题,因为所有TLS 1.3套件都很强大。但是,如果你想做一些不同寻常的事情,也许可以启用当前默认禁用的 CCM
套件,你必须通过配置文件更改OpenSSL默认值来使用一种解决方法,我将在下一节中介绍。
偶尔,您在尝试配置某些应用程序以某种方式使用OpenSSL时会遇到问题,如果没有配置选项来实现您所需的功能,您会感到沮丧。在这种情况下,您可以更改OpenSSL默认值。
启动时,OpenSSL将经历一个初始化过程,尝试从文件系统中获取默认值。该程序包括以下步骤:
OPENSSL_CONF
环境变量,该变量应包含配置文件的路径。如果二进制文件设置了 setuid
或 setguid
标志,则跳过此步骤。此过程确保有许多选项可用于以解决特定需求的方式控制默认值。我们可以仅更改一个程序或在同一服务器上运行的所有程序的默认配置。
对于后一种用例,使用版本工具确定默认配置文件的位置:
xxxxxxxxxx
$ openssl version -d
OPENSSLDIR: "/usr/lib/ssl"
现在我们知道如何更改默认值,问题就变成了在配置文件中放入什么。有关配置文件的语法和详细信息,最好查阅官方文档。但是,如果您只需要重新配置密码套件配置,请查看以下示例:
xxxxxxxxxx
[default_conf]
ssl_conf = ssl_section
[ssl_section]
system_default = system_default_section
[system_default_section]
MinProtocol = TLSv1.2
CipherString = DEFAULT:@SECLEVEL=2
Ciphersuites = TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256
Options = ServerPreference,PrioritizeChaCha
此配置文件指定了支持的最低协议、安全级别、传统密码套件配置和TLS 1.3套件配置,它还启用了特殊的ChaCha20优先级,如果OpenSSL检测到客户端更喜欢此密码而不是AES,则会触发此优先级。有关可用参数的完整列表,请参阅官方文档。
当涉及到密码套件配置时,最好的方法是避免传统的基于关键字的套件配置,而是明确指定要使用的套件。通过这样做,您不必了解复杂的关键字行为,可以最大限度地减少错误,并且还可以留下一个自文档化且易于理解的配置。
在本节中,我将向您提供我的建议并解释我的推理。为了简单起见,我将把这些套件显示为一个有序的列表,尽管它们分别针对TLS 1.3和早期协议版本进行了配置。以下是我为所有TLS服务推荐的默认配置,按优先顺序列出套件:
xxxxxxxxxx
TLS_AES_128_GCM_SHA256
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_256_GCM_SHA384
ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-ECDSA-CHACHA20-POLY1305
ECDHE-ECDSA-AES256-GCM-SHA384
ECDHE-ECDSA-AES128-SHA
ECDHE-ECDSA-AES256-SHA
ECDHE-ECDSA-AES128-SHA256
ECDHE-ECDSA-AES256-SHA384
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-RSA-CHACHA20-POLY1305
ECDHE-RSA-AES256-GCM-SHA384
ECDHE-RSA-AES128-SHA
ECDHE-RSA-AES256-SHA
ECDHE-RSA-AES128-SHA256
ECDHE-RSA-AES256-SHA384
DHE-RSA-AES128-GCM-SHA256
DHE-RSA-CHACHA20-POLY1305
DHE-RSA-AES256-GCM-SHA384
DHE-RSA-AES128-SHA
DHE-RSA-AES256-SHA
DHE-RSA-AES128-SHA256
DHE-RSA-AES256-SHA256
此配置仅使用支持前向保密(forward secrecy)并提供强加密的套件。首选128位套件,它更快,提供强安全ECDSA公钥加密,比传统RSA(在通常的密钥长度下)更快、更安全;ECDHE密钥交换,比DHE更快;以及比旧CBC模式更快、更安全的认证加密。
可以采用更短的套件配置,例如,删除256位套件以及使用DHE进行密钥交换的套件。然而,我发现,拥有稍微多样化的套件有助于避免挑剔客户的各种边缘情况。
在互操作性方面,所有现代浏览器和客户端都应该能够连接。一些非常老的客户端可能不会,但我们谈论的是过时的平台,例如在Windows XP上运行的Internet Explorer。如果您真的需要支持此功能,则需要将使用过时功能(如3DES或RSA密钥交换)的套件附加到列表中。
最后,在性能方面,您可以使用最后一个技巧:告诉OpenSSL对更喜欢这种密码而不是AES的客户端使用ChaCha20。您会注意到,在我的配置中,总是有一个ChaCha20套件遵循128位AES-GCM套件。对于大多数客户来说,AES-GCM是正确的选择,但ChaCha20对于一些移动客户端来说是更好的选择,因为它们可以更快地完成。通过ChaCha20优先级,您可以为这些移动客户端提供更好的体验(更快的加载时间)。
OpenSSL为此提供的选项称为 PrioritizeChaCha
。此功能是一个相对较新的配置选项,您会发现并非所有服务器软件都可以控制它。例如,在撰写本文时,Apache可以(使用 SSLOpenSSLConfCmd
),但Nginx不能。如前一节所述,在后一种情况下,通过更改OpenSSL默认值应该可以解决问题。
xxxxxxxxxx
备注
在实践中,大多数系统不需要配置为支持最佳性能或移动客户端体验。如果你喜欢让你的TLS配置恰到好处,那么一定要遵循本节的所有建议。然而,通常情况下,你不应该花太多时间进行微调。如果你发现自己的平台不支持TLS 1.3套件配置,或者无法优先考虑ChaCha20,只需使用默认值并继续前进。
DH密钥交换已经过时,但从哲学角度来看,您可能仍然希望在服务器中支持它。如果你这样做,你可能会发现一些服务器软件(例如Nginx)需要手动配置所需的DH参数。这是如何:
xxxxxxxxxx
$ openssl dhparam -out dh-2048.pem 2048
在实践中,只有2048位DH参数是有意义的。任何少的东西都会让你变得软弱或不安全,而任何多的东西都将让你放慢速度。DH参数不需要保密。事实上,有一些预定义的组(有时称为众所周知的组)是推荐的,因为已知它们是安全生成的。
很少,您可能会遇到这样的情况,通常是在传统环境中,您需要使用1024位DH参数配置服务器。在这种情况下,你不能使用一个知名的组。问题是弱DH组容易受到预计算攻击,这进一步降低了它们的安全性。如果您真的必须使用1024位DH参数,请始终使用OpenSSL生成您自己的唯一组。
在本节中,我将简要介绍适用于TLS 1.2和早期协议版本的基于遗留关键字的密码套件配置。只有当你对关键字方法的工作原理感兴趣时,这一部分才很重要。否则,您最好简单地指定要使用的套件,就像我在上一节中推荐的配置一样。
密码套件 keywords(关键字)是密码套件配置的基本构建块。每个套件名称(例如RC4-SHA)都是一个关键字,可以选择一个套件。所有其他关键字都根据一些标准选择套件组。关键字名称区分大小写。在本节中,我将逐一概述OpenSSL支持的所有密码套件关键字。
HIGH
将只选择非常强的密码套件。关键字 | 含义 |
---|---|
DEFAULT | 默认密码列表。这是在编译时确定的,并且必须是指定的第一个密码字符串。 |
COMPLEMENTOFDEFAULT | ALL中包含的密码,但默认情况下未启用。请注意,此规则不包括 eNULL ,eNULL 不包含在 ALL 中(必要时使用 COMPLEMENTOFALL )。 |
ALL | 除 eNULL 密码外的所有密码套件,必须显式启用。 |
COMPLEMENTOFALL | ALL (当前为 eNULL )未启用密码套件。 |
HIGH | “High”-加密密码套件。这目前意味着密钥长度大于128位的那些,以及一些具有128位密钥的密码套件。 |
MEDIUM | “Medium”-加密密码套件,目前其中一些使用128位加密。 |
LOW | “Low”-加密密码套件,目前使用64位或56位加密算法,但不包括导出密码套件。不再支持。不安全 |
EXP,EXPORT | 导出加密算法。包括40位和56位算法。不再支持。 不安全的。 |
EXPORT40 | 40位导出加密算法。不再支持。不安全的。 |
EXPORT56 | 56位导出加密算法。不再支持。不安全的。 |
TLSv1.2, TLSv1.0, TLSv1, SSLv3, SSLv2 | 需要指定协议版本的密码套件。TLS 1.0有两个关键字,TLS 1.3和TLS 1.1没有关键字。这些关键字不会影响协议配置,只会影响套件。 |
关键字 | 含义 |
---|---|
MD5 | 使用MD5的密码套件。过时且不安全。 |
SHA, SHA1 | 使用SHA1的密码套件。 |
SHA256 | 使用SHA256的密码套件。 |
SHA384 | 使用SHA384的密码套件。 |
xxxxxxxxxx
备注
摘要算法关键字仅选择在协议级别验证数据完整性的套件。TLS 1.2引入了对身份验证加密的支持,这是一种将加密与完整性验证捆绑在一起的机制。当使用所谓的AEAD(authenticated encryption with associated Data,具有相关数据的身份验证加密)套件时,该协议不需要提供额外的完整性验证。因此,您将无法使用摘要算法关键字来选择AEAD套件(目前,名称中包含GCM的套件)。这些套件的名称确实使用了SHA256和SHA384后缀,但(尽管可能令人困惑)在这里,它们指的是用于构建与套件一起使用的伪随机函数的哈希函数。
关键字 | 含义 |
---|---|
aDH | 密码套件有效地使用DH身份验证,即证书携带DH密钥。在1.1.0中删除。 |
aDSS、DSS | 使用DSS身份验证的密码套件,即证书携带DSS密钥。 |
aECDH | 使用ECDH身份验证的密码套件。在1.1.0中删除。 |
aECDSA, ECDSA | 使用ECDSA身份验证的密码套件。 |
aNULL | 密码套件不提供身份验证。这是目前的匿名DH算法。不安全的。 |
aRSA | 使用RSA身份验证的密码套件,即证书携带RSA密钥。 |
aPSK | 使用PSK(预共享密钥)身份验证的密码套件。 |
aSRP | 使用SRP(安全远程密码)身份验证的密码套件。 |
关键字 | 含义 |
---|---|
ADH | 匿名(anonymous)DH密码套件。不安全的。 |
AECDH | 匿名ECDH密码套件。不安全。 |
DHE, EDH | 仅使用临时DH密钥协商的密码套件。 |
ECDHE, EECDH | 使用临时ECDH的密码套件。 |
kDHE, kEDH, DH | 使用临时DH密钥协商的密码套件(包括匿名DH)。 |
kECDHE, kEECDH, ECDH | 使用临时ECDH密钥协商的密码套件(包括匿名ECDH)。 |
kRSA, RSA | 使用RSA密钥交换的密码套件。 |
kPSK, kECDHEPSK, kDHEPSK, kRSAPSK | 使用PSK密钥交换的密码套件。 |
关键字 | 含义 |
---|---|
AES, AESCCM, AESCCM8, AESGCM | 使用AES、AES CCM和AES GCM的密码套件。 |
ARIA, ARIA128, ARIA256 | 使用ARIA的密码套件。 |
CAMELLIA, CAMELLIA128, CAMELLIA256 | 使用Camellia的密码套件。过时的。 |
CHACHA20 | 使用ChaCha20的密码套件。 |
eNULL, NULL | 不使用加密的密码套件。不安全的。 |
IDEA | 使用IDEA的密码套件。过时的。 |
SEED | 使用SEED的密码套件。过时的。 |
3DES, DES, IDEA, RC2, RC4 | 默认情况下不再支持。过时且不安全。 |
关键字 | 含义 |
---|---|
@SECLEVEL | 配置安全级别,该级别设置最低安全要求。 |
@STRENGTH | 按加密算法密钥长度的顺序对当前密码套件列表进行排序。 |
aGOST | 使用GOST R 34.10(2001或94)进行身份验证的密码套件。需要具有GOST功能的引擎。 |
aGOST01 | 使用GOST R 34.10-2001身份验证的密码套件。 |
aGOST94 | 使用GOST R 34.10-94身份验证的密码套件。过时的。请改用GOST R 34.10-2001。 |
kGOST | 使用RFC 4357中指定的VKO 34.10密钥交换的密码套件。 |
GOST94 | 使用基于GOST R 34.11-94的HMAC的密码套件。 |
GOST89MAC | 使用GOST 28147-89 MAC而不是HMAC的密码套件。 |
PSK | 在任何容量下使用PSK的密码套件。 |
在大多数情况下,您将单独使用关键字,但也可以通过将两个或多个关键字与 +
字符连接,将它们组合起来,只选择满足多种要求的套件。在以下示例中,我们选择将ECDHE密钥交换与AES-GCM结合使用的套件:
xxxxxxxxxx
$ openssl ciphers -v -s -tls1_2 'EECDH+AESGCM'
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(128) Mac=AEAD
ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD
构建密码套件配置的关键概念是当前套件列表(current suite list)。列表始终以空开头,没有任何套件,但您添加到配置字符串中的每个关键字都会以某种方式更改列表。默认情况下,新套件将附加到列表中。在以下示例中,配置从使用ECDHE密钥交换的所有套件开始,然后是使用DHE密钥交换的全部套件:
xxxxxxxxxx
$ openssl ciphers -v 'ECDHE:DHE'
冒号字符通常用于分隔关键字,但空格和逗号同样可以接受。以下命令产生与前一个示例相同的输出:
xxxxxxxxxx
$ openssl ciphers -v 'ECDHE DHE'
关键字编辑器是您可以放置在每个关键字开头的字符,以便将默认操作(添加到列表中)更改为其他操作。支持以下操作:
Append——追加
将套件添加到列表末尾。如果任何套件已经在名单上,它们将保持在目前的位置。这是默认操作,当关键字前面没有修饰符时调用。
Delete(-)——删除
从列表中删除所有匹配的套件,可能允许其他关键字稍后重新引入它们。
Permanently delete(!)——永久删除
从列表中删除所有匹配的套件,并防止它们以后被其他关键字添加。此修饰符可用于指定您永远不想使用的所有套件,使进一步的选择更容易并防止错误。
Move to the end(+)——移动到末尾
将所有匹配的套件移动到列表末尾。这仅适用于现有套房;它从不在列表中添加新的套房。如果你想保持一些较弱的套件启用,但更喜欢较强的套件,这个修改器很有用。例如,字符串 AES:+AES256
启用所有AES套件,但将256位套件推到末尾。
@STRENGTH
关键字有一个特殊的用途:它不会引入或删除任何套件,但会按照密码强度降序对其进行排序。自动排序是一个有趣的想法,但它只有在一个完美的世界里才有意义,在这个世界里,密码套件实际上可以仅通过密码强度进行比较。在大多数情况下,通常不需要最高强度的套件。您经常在配置中使用它们,只是为了与挑剔的客户端进行互操作。
在配置过程中,您可能会遇到两种类型的错误。第一种是拼写错误或试图使用不存在的关键字的结果:
xxxxxxxxxx
$ openssl ciphers -v '@HIGH'
Error in cipher list
140460843755168:error:140E6118:SSL routines:SSL_CIPHER_PROCESS_RULESTR:invalid command:ssl_ciph.c:1317:
输出很神秘,但它确实包含一条错误消息。
另一种可能性是,您最终会得到一个空的密码套件列表,在这种情况下,您可能会看到类似于以下内容的内容:
xxxxxxxxxx
$ openssl ciphers -v 'SHA512'
Error in cipher list
140202299557536:error:1410D0B9:SSL routines:SSL_CTX_set_cipher_list:no cipher match:ssl_lib.c:1312:
如您所知,计算速度是任何加密操作的重要限制因素。OpenSSL附带了一个内置的基准测试工具,您可以使用它来了解系统的功能和限制。您可以使用 speed
命令调用基准。
如果您在不使用任何参数的情况下调用 speed
,OpenSSL会产生大量输出,其中很少有人会感兴趣。更好的方法是只测试与您直接相关的算法。例如,在安全的web服务器中使用时,您可能会关心RSA和ECDSA的性能,并会这样做:
xxxxxxxxxx
$ openssl speed rsa ecdsa
结果输出的第一部分由OpenSSL版本号和编译时配置组成。此信息对于记录保存非常有用,如果您正在测试不同版本的OpenSSL:
xxxxxxxxxx
OpenSSL 1.1.1f 31 Mar 2020
built on: Mon Apr 20 11:53:50 2020 UTC
options:bn(64,64) rc4(16x,int) des(int) aes(partial) blowfish(ptr)
compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -Wa,--noexecstack -g -O2 -fdebug-prefix-map=/build/openssl-P_ODHM/openssl-1.1.1f=. -fstack-protector-strong -Wformat -Werror=format-security -DOPENSSL_TLS_SECURITY_LEVEL=2 -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DRC4_ASM -DMD5_ASM -DAESNI_ASM -DVPAES_ASM -DGHASH_ASM -DECP_NISTZ256_ASM -DX25519_ASM -DPOLY1305_ASM -DNDEBUG -Wdate-time -D_FORTIFY_SOURCE=2
输出的其余部分包含基准测试结果。让我们先来看看RSA密钥操作:
xxxxxxxxxx
sign verify sign/s verify/s
rsa 512 bits 0.000073s 0.000005s 13736.4 187091.4
rsa 1024 bits 0.000207s 0.000014s 4828.4 71797.6
rsa 2048 bits 0.000991s 0.000045s 1009.1 22220.4
rsa 3072 bits 0.004796s 0.000096s 208.5 10463.5
rsa 4096 bits 0.011073s 0.000165s 90.3 6054.5
rsa 7680 bits 0.090541s 0.000565s 11.0 1769.7
rsa 15360 bits 0.521500s 0.002204s 1.9 453.7
RSA最常用于2048位。在我的结果中,被测试服务器的一个CPU每秒可以执行大约1000个签名(服务器)操作和22000个验证(客户端)操作。至于ECDSA,它通常只使用256位。我们可以看到,在这个长度上,ECDSA可以完成10倍的签名。另一方面,在验证方面速度较慢,每秒仅6500次操作:
xxxxxxxxxx
sign verify sign/s verify/s
256 bits ecdsa (nistp256) 0.0000s 0.0002s 20508.1 6566.2
384 bits ecdsa (nistp384) 0.0017s 0.0013s 580.4 755.0
521 bits ecdsa (nistp521) 0.0006s 0.0012s 1711.5 840.8
【在我的NAS中:
NAS为双核,当使用 -multi 2
参数时,签名速度和验证速度分别有近93%左右的提升:
xxxxxxxxxx
sign verify sign/s verify/s
rsa 2048 bits 0.002520s 0.000066s 396.9 15054.3
256 bits ecdsa (nistp256) 0.0001s 0.0002s 10609.8 4686.5
253 bits EdDSA (Ed25519) 0.0002s 0.0005s 6626.0 2117.0
456 bits EdDSA (Ed448) 0.0002s 0.0006s 4999.9 1547.9
在飞牛NAS中(四核四线程的i3-9300,运行时CPU温度达到100℃)使用 -multi 4
,结果如下:
xxxxxxxxxx
sign verify sign/s verify/s
rsa 2048 bits 0.000130s 0.000004s 7714.7 266322.2
256 bits ecdsa (nistp256) 0.0000s 0.0000s 192155.9 69567.0
253 bits EdDSA (Ed25519) 0.0000s 0.0000s 99364.5 36861.0
456 bits EdDSA (Ed448) 0.0001s 0.0001s 18393.5 18872.7
AWS云主机使用 -multi 2
参数的结果如下:
xxxxxxxxxx
sign verify sign/s verify/s
rsa 2048 bits 0.000790s 0.000033s 1266.0 30731.6
256 bits ecdsa (nistp256) 0.0000s 0.0001s 27154.9 10272.4
253 bits EdDSA (Ed25519) 0.0001s 0.0002s 15865.7 6254.7
456 bits EdDSA (Ed448) 0.0003s 0.0003s 3560.7 3102.6
】
在实践中,您更关心签名操作,因为服务器旨在为许多客户端提供服务。另一方面,客户端通常同时只与少数服务器通信。在这种情况下,ECDSA较慢的事实并不重要。
这个速度输出有什么用?您应该能够比较编译时选项如何影响速度,或者不同版本的OpenSSL如何在同一平台上进行比较。
如果你想切换服务器,对OpenSSL进行基准测试可以让你了解计算能力的差异。您还可以验证硬件加速是否到位。
使用基准测试结果来估计部署性能并不简单,因为现实生活中影响性能的因素很多。此外,其中许多因素都在TLS之外(例如,HTTP保持活动设置、缓存等)。充其量,这些数字只能用于粗略估计。
但在你这样做之前,你需要考虑其他事情。默认情况下, speed
命令将仅使用单个进程。大多数服务器都有多个内核,因此要了解整个服务器支持多少TLS操作,您必须指示速度并行使用多个实例。您可以使用 -multi
开关来实现这一点。我的服务器有两个核心,所以这就是我要指定的:
xxxxxxxxxx
$ openssl speed -multi 2 rsa
[...]
sign verify sign/s verify/s
rsa 512 bits 0.000037s 0.000003s 27196.5 367409.6
rsa 1024 bits 0.000106s 0.000007s 9467.8 144188.0
rsa 2048 bits 0.000503s 0.000023s 1988.1 43838.4
rsa 3072 bits 0.002415s 0.000050s 414.1 20152.2
rsa 4096 bits 0.005589s 0.000084s 178.9 11880.8
rsa 7680 bits 0.045659s 0.000285s 21.9 3506.1
rsa 15360 bits 0.264904s 0.001130s 3.8 884.8
正如预期的那样,性能大约好两倍。我再次关注每秒可以完成多少RSA签名,因为这是服务器上执行的CPU密集型加密操作,因此始终是第一个瓶颈。1988个签名/秒(使用2048位密钥)的结果告诉我们,这个小型服务器每秒肯定会处理数百个全新的TLS连接。(我们必须假设服务器会做其他事情,而不仅仅是TLS握手。)就我而言,这就足够了——有一个非常健康的安全裕度。因为我在服务器上也启用了会话恢复,并且绕过了公共加密,所以我知道性能会更好。
在测试速度时,务必使用 -evp
开关启用硬件加速。如果你不这样做,结果可能会大不相同。作为示例,看看支持AES-NI硬件加速的服务器上的性能差异。我通过纯软件实现得到了以下结果:
xxxxxxxxxx
$ openssl speed aes-128-cbc
[...]
The 'numbers' are in 1000s of bytes per second processed.
type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes
aes-128 cbc 131377.50k 135401.41k 134796.12k 133931.35k 134778.95k
硬件加速后,性能提高了三倍多:
xxxxxxxxxx
$ openssl speed -evp aes-128-cbc
[...]
The 'numbers' are in 1000s of bytes per second processed.
type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes
aes-128-cbc 421949.23k 451223.42k 460066.13k 463651.84k 462883.50k
当你考虑加密操作的速度时,你应该关注你实际部署的原语。例如,CBC已经过时,所以您想在GCM模式下使用AES。在这里,我们看到GCM的性能提高了三到四倍:
xxxxxxxxxx
$ openssl speed -evp aes-128-gcm
[...]
The 'numbers' are in 1000s of bytes per second processed.
type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes
aes-128-gcm 219599.85k 588822.40k 1313242.97k 1680529.75k 1989388.97k
然后是ChaCha20-Poly1305,这是一个相对较新的添加。它的性能无法与硬件加速的AES竞争,但它不需要;这种经过身份验证的密码在手机上设计得很快。将其速度与未加速的AES-128-CBC进行比较。
xxxxxxxxxx
$ openssl speed -evp chacha20-poly1305
[...]
The 'numbers' are in 1000s of bytes per second processed.
type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes
chacha20-poly1305 148729.65k 273026.35k 590953.90k 1027021.82k 1092427.78k
xxxxxxxxxx
备注
从OpenSSL 3.0开始,无论是否存在-evp开关,只要CPU支持,就始终使用硬件加速。
如果你想设置自己的CA,你需要的一切都已经包含在OpenSSL中。用户界面纯粹是基于命令行的,因此对用户不太友好,但这可能会更好。经历这个过程是非常有教育意义的,因为它迫使你思考每一个方面,甚至是最小的细节。
设置私人CA的教育方面是我建议这样做的主要原因,但还有其他原因。基于OpenSSL的CA,尽管可能很粗糙,但可以很好地满足个人或小团体的需求。例如,在开发环境中使用私有CA比在任何地方使用自签名证书要好得多。同样,提供双因素身份验证的客户端证书可以显著提高敏感web应用程序的安全性。
运行私有CA的最大挑战不是设置所有内容,而是保持基础设施的安全。例如,根密钥必须保持脱机状态,因为所有安全都依赖于它。另一方面,CRL和OCSP响应者证书必须定期刷新,这需要使根联机。
在阅读本节时,您将创建两个配置文件:一个用于控制根CA( root-ca.conf ),另一个用于管理从属CA( sub-ca.conf )。虽然你只需按照我的说明就可以从头开始做任何事情,但你也可以从我的GitHub帐户下载配置文件模板。后一种选择会节省你一些时间,但前一种方法会让你更好地理解所涉及的工作。
在本节的其余部分,我们将创建一个在结构上与公共CA相似的私有CA。将有一个根CA,可以从中创建其他从属CA。我们将通过CRL和OCSP响应程序提供撤销信息。为了使根CA保持离线,OCSP响应者将拥有自己的身份。这不是你能拥有的最简单的私有CA,但它是一个可以正确保护的CA。作为奖励,下级CA将受到技术限制(technically constrained),这意味着它只允许为允许的主机名颁发证书。
安装完成后,必须将根证书安全地分发给所有预期的客户端。一旦根就绪,您就可以开始颁发客户端和服务器证书。这种设置的主要局限性是OCSP响应器主要是为测试而设计的,只能用于较轻的负载。
创建新的CA涉及几个步骤:配置、创建目录结构和初始化密钥文件,最后生成根密钥和证书。本节描述了该过程以及常见的CA操作。
在实际创建CA之前,我们需要准备一个配置文件( root-ca.conf ),该文件将告诉OpenSSL我们希望如何设置。在正常使用过程中,大多数时候不需要配置文件,但在涉及复杂操作(如创建根CA)时,它们是必不可少的。OpenSSL配置文件功能强大;在继续之前,我建议您熟悉它们的功能(命令行上的 man config
)。
配置文件的第一部分包含一些基本的CA信息,如名称和基本URL,以及CA可分辨名称的组成部分。由于语法灵活,信息只需要提供一次:
xxxxxxxxxx
[default]
name = root-ca
domain_suffix = example.com
aia_url = http://$name.$domain_suffix/$name.crt
crl_url = http://$name.$domain_suffix/$name.crl
ocsp_url = http://ocsp.$name.$domain_suffix:9080
default_ca = ca_default
name_opt = utf8,esc_ctrl,multiline,lname,align
[ca_dn]
countryName = "GB"
organizationName = "Example"
commonName = "Root CA"
第二部分直接控制CA的运营。有关每个设置的完整信息,请参阅ca命令的文档(命令行上的 man ca
)。大多数设置都是不言自明的;我们主要告诉OpenSSL我们想把文件放在哪里。因为这个根CA将仅用于颁发下级CA,所以我选择证书的有效期为10年。对于签名算法,默认情况下使用安全的SHA256。
默认策略( policy_c_o_match
)已配置,从此CA颁发的所有证书都具有与CA匹配的 countryName
和 organizationName
字段。这通常不会由公共CA完成,但适用于私有CA:
xxxxxxxxxx
[ca_default]
home = .
database = $home/db/index
serial = $home/db/serial
crlnumber = $home/db/crlnumber
certificate = $home/$name.crt
private_key = $home/private/$name.key
RANDFILE = $home/private/random
new_certs_dir = $home/certs
unique_subject = no
copy_extensions = none
default_days = 3650
default_crl_days = 365
default_md = sha256
policy = policy_c_o_match
[policy_c_o_match]
countryName = match
stateOrProvinceName = optional
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
第三部分包含 req
命令的配置,该命令在创建自签名根证书期间只使用一次。最重要的部分在扩展中:basicConstraints
扩展表示证书是CA,keyUsage
包含此场景的适当设置:
xxxxxxxxxx
[req]
default_bits = 4096
encrypt_key = yes
default_md = sha256
utf8 = yes
string_mask = utf8only
prompt = no
distinguished_name = ca_dn
req_extensions = ca_ext
[ca_ext]
basicConstraints = critical,CA:true
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash
配置文件的第四部分包含在构建根CA颁发的证书期间将使用的信息。所有证书都将是CA,如 basicConstraints
扩展所示,但我们将 pathlen
设置为零,这意味着不允许有更多的从属CA。
所有下级CA都将受到限制,这意味着他们颁发的证书仅对域名的一个子集有效,并且使用受到限制。首先,扩展的 KeyUsage
扩展仅指定了 clientAuth
和 serverAuth
,即TLS客户端和服务器身份验证。其次,nameConstraints
扩展将允许的主机名限制为 example.com
和 example.org
域名。理论上,这种设置使您能够将从属CA的控制权交给其他人,但仍然可以安全地知道他们不能为任意主机名颁发证书。如果需要,可以将每个从属CA限制为一个小的域命名空间。排除这两个IP地址范围的要求来自CA/浏览器论坛的基线要求,该要求对技术上受限制的下属CA进行了定义。
在实践中,名称约束并不完全实用,因为一些主要平台目前无法识别 nameConstraints
扩展。如果您将此扩展标记为关键,则此类平台将拒绝您的证书。如果你不将其标记为关键(如示例中所示),你就不会遇到这样的问题,但其他一些平台不会强制执行。
xxxxxxxxxx
[sub_ca_ext]
authorityInfoAccess = @issuer_info
authorityKeyIdentifier = keyid:always
basicConstraints = critical,CA:true,pathlen:0
crlDistributionPoints = @crl_info
extendedKeyUsage = clientAuth,serverAuth
keyUsage = critical,keyCertSign,cRLSign
nameConstraints = @name_constraints
subjectKeyIdentifier = hash
[crl_info]
URI.0 = $crl_url
[issuer_info]
caIssuers;URI.0 = $aia_url
OCSP;URI.0 = $ocsp_url
[name_constraints]
permitted;DNS.0=example.com
permitted;DNS.1=example.org
excluded;IP.0=0.0.0.0/0.0.0.0
excluded;IP.1=0:0:0:0:0:0:0:0/0:0:0:0:0:0:0:0
配置的第五部分也是最后一部分指定了与OCSP响应签名证书一起使用的扩展。为了能够运行OCSP响应程序,我们生成了一个特殊的证书,并将OCSP签名功能委托给它。此证书不是CA,您可以从扩展中看到:
xxxxxxxxxx
[ocsp_ext]
authorityKeyIdentifier = keyid:always
basicConstraints = critical,CA:false
extendedKeyUsage = OCSPSigning
keyUsage = critical,digitalSignature
subjectKeyIdentifier = hash
下一步是创建上一节中指定的目录结构,并初始化CA操作期间将使用的一些文件:
xxxxxxxxxx
$ mkdir root-ca
$ cd root-ca
$ mkdir certs db private
$ chmod 700 private
$ touch db/index
$ openssl rand -hex 16 > db/serial
$ echo 1001 > db/crlnumber
使用以下子目录:
certs/
证书存储;新证书颁发后将放在这里。
db/
此目录用于证书数据库(索引)和保存下一个证书和CRL序列号的文件。OpenSSL将根据需要创建一些附加文件。
private/
此目录将存储私钥,一个用于CA,另一个用于OCSP响应器。重要的是,没有其他用户可以访问它。(事实上,如果你想认真对待CA,存储根材料的机器应该只有最少数量的用户帐户。)
我们采取两个步骤来创建根CA。首先,我们生成密钥和CSR。当我们使用 -config
开关时,所有必要的信息都将从配置文件中获取:
xxxxxxxxxx
$ openssl req -new \
-config root-ca.conf \
-out root-ca.csr \
-keyout private/root-ca.key
在第二步中,我们创建一个自签名证书。-extensions
开关指向配置文件中的 ca_ext
部分,该部分激活适用于根ca的扩展:
xxxxxxxxxx
$ openssl ca -selfsign \
-config root-ca.conf \
-in root-ca.csr \
-out root-ca.crt \
-extensions ca_ext
db/index 中的数据库是一个明文文件,其中包含证书信息,每行一个证书。创建根CA后,它应该只包含一行:
xxxxxxxxxx
V 240706115345Z 1001 unknown /C=GB/O=Example/CN=Root CA
每行包含六个由制表符分隔的值:
unknown
表示不知道文件位置要从新CA生成CRL,请使用CA命令的 -gencrl
开关:
xxxxxxxxxx
$ openssl ca -gencrl \
-config root-ca.conf \
-out root-ca.crl
要颁发证书,请使用所需的参数调用ca命令。重要的是, -extensions
开关指向配置文件中的正确部分(例如,您不想创建另一个根CA)。
xxxxxxxxxx
$ openssl ca \
-config root-ca.conf \
-in sub-ca.csr \
-out sub-ca.crt \
-extensions sub_ca_ext
要吊销(revoke)证书,请使用 ca
命令的 -revoke
开关;您需要一份要吊销的证书副本。因为所有证书都存储在 certs/ 目录中,所以您只需要知道序列号。如果你有一个可分辨的名字,你可以在数据库中查找序列号。
为 -crl_reason
开关中的值选择正确的原因。该值可以是以下值之一:
unspecified
(未指定)keyCompromise
(密钥妥协)CACompromise
(CA妥协) affiliationChanged
(附属关系更改)superseded
(取代)cessationOfOperation
(操作终止)certificateHold
(证书持有)removeFromCRL
(从CRL中删除)xxxxxxxxxx
$ openssl ca \
-config root-ca.conf \
-revoke certs/1002.pem \
-crl_reason keyCompromise
首先,我们为OCSP响应者创建一个密钥和CSR。这两个操作与任何非CA证书一样完成,这就是为什么我们不指定配置文件:
xxxxxxxxxx
$ openssl req -new \
-newkey rsa:2048 \
-subj "/C=GB/O=Example/CN=OCSP Root Responder" \
-keyout private/root-ocsp.key \
-out root-ocsp.csr
其次,使用根CA颁发证书。-extensions
开关的值指定 ocsp_ext
,这可确保设置适合OSCP签名的扩展。我将新证书的有效期从默认的3650天缩短到365天。因为这些OCSP证书不包含吊销信息,所以无法吊销。因此,你希望尽可能缩短生命周期。一个好的选择是30天,前提是您准备生成一个新的证书,通常:
xxxxxxxxxx
$ openssl ca \
-config root-ca.conf \
-in root-ocsp.csr \
-out root-ocsp.crt \
-extensions ocsp_ext \
-days 30
现在,您已经准备好启动OCSP响应程序。对于测试,您可以在根CA所在的同一台机器上进行。但是,对于生产,您必须将OCSP响应程序密钥和证书移动到其他地方:
xxxxxxxxxx
$ openssl ocsp \
-port 9080
-index db/index \
-rsigner root-ocsp.crt \
-rkey private/root-ocsp.key \
-CA root-ca.crt \
-text
您可以使用以下命令行测试OCSP响应程序的操作:
xxxxxxxxxx
$ openssl ocsp \
-issuer root-ca.crt \
-CAfile root-ca.crt \
-cert root-ocsp.crt \
-url http://127.0.0.1:9080
在输出中,verify OK
表示签名已正确验证,good
表示证书未被吊销。
xxxxxxxxxx
Response verify OK
root-ocsp.crt: good
This Update: Jul 9 18:45:34 2014 GMT
下级CA生成过程在很大程度上反映了根CA过程。在本节中,我将仅在适当的情况下强调差异。有关其他内容,请参阅上一节。
要为从属ca生成配置文件(sub-ca.conf),请从我们用于根CA的文件开始,并进行本节中列出的更改。我们将把名称更改为 subca
,并使用不同的可分辨名称(distinguished name)。我们将把OCSP响应器放在不同的端口上,但这只是因为 ocsp
命令不理解虚拟主机。如果您为OCSP响应程序使用了合适的web服务器,则可以完全避免使用特殊端口。新证书的默认生存期为365天,我们将每30天生成一次新的CRL。
将 copy_extensions
更改为 copy
意味着CSR的扩展将被复制到证书中,但前提是它们尚未在我们的配置中设置。通过此更改,无论谁准备CSR,都可以在其中添加所需的替代名称,并且其中的信息将被提取并放置在证书中。此功能有点危险(您允许其他人对证书中的内容进行有限的直接控制),但我认为它适用于较小的环境:
xxxxxxxxxx
[default]
name = sub-ca
ocsp_url = http://ocsp.$name.$domain_suffix:9081
[ca_dn]
countryName = "GB"
organizationName = "Example"
commonName = "Sub CA"
[ca_default]
default_days = 365
default_crl_days = 30
copy_extensions = copy
在配置文件的末尾,我们将添加两个新的配置文件,分别用于客户端和服务器证书。唯一的区别在于 keyUsage
和 extendedKeyUsage
扩展。请注意,我们指定了 basicConstraints
扩展,但将其设置为 false
。我们这样做是因为我们正在从CSR复制扩展。如果我们省略了这个扩展,我们最终可能会使用CSR中指定的一个:
xxxxxxxxxx
[server_ext]
authorityInfoAccess = @issuer_info
authorityKeyIdentifier = keyid:always
basicConstraints = critical,CA:false
crlDistributionPoints = @crl_info
extendedKeyUsage = clientAuth,serverAuth
keyUsage = critical,digitalSignature,keyEncipherment
subjectKeyIdentifier = hash
[client_ext]
authorityInfoAccess = @issuer_info
authorityKeyIdentifier = keyid:always
basicConstraints = critical,CA:false
crlDistributionPoints = @crl_info
extendedKeyUsage = clientAuth
keyUsage = critical,digitalSignature
subjectKeyIdentifier = hash
在您对配置文件满意后,按照与根CA相同的过程创建一个目录结构。只需使用不同的目录名,例如 sub-ca
。
如前所述,我们采取两个步骤来创建从属CA。首先,我们生成密钥和CSR。当我们使用 -config
开关时,所有必要的信息都将从配置文件中获取。
xxxxxxxxxx
$ openssl req -new \
-config sub-ca.conf \
-out sub-ca.csr \
-keyout private/sub-ca.key
在第二步中,我们让根CA颁发证书。-extensions
开关指向配置文件中的 sub_ca_ext
部分,该部分激活适用于从属CA的扩展。
xxxxxxxxxx
$ openssl ca \
-config root-ca.conf \
-in sub-ca.csr \
-out sub-ca.crt \
-extensions sub_ca_ext
要颁发服务器证书,请在 -extensions
开关中指定 server_ext
的同时处理CSR:
xxxxxxxxxx
$ openssl ca \
-config sub-ca.conf \
-in server.csr \
-out server.crt \
-extensions server_ext
要颁发客户端证书,请在 -extensions
开关中指定 client_ext
的同时处理CSR:
xxxxxxxxxx
$ openssl ca \
-config sub-ca.conf \
-in client.csr \
-out client.crt \
-extensions client_ext
xxxxxxxxxx
当请求新证书时,在操作完成之前,将向您提供其所有信息以供验证。你应该始终确保一切正常,尤其是如果你正在与别人准备的CSR合作。特别注意证书的可分辨名称和 `basicConstraints` (基本约束)以及 `subjectAlternativeName`(主体可选名称)扩展。
CRL生成和证书吊销与根CA相同。OCSP响应器唯一不同的是端口;下级CA应改用 9081
。建议响应者使用自己的证书,这样可以避免将从属CA保留在公共服务器上。
由于大量的协议功能和实现怪癖,有时很难确定安全服务器的确切配置和功能。尽管存在许多用于此目的的工具,但通常很难确切地知道它们是如何工作的,这有时使得很难完全信任它们的结果。尽管我花了数年时间测试安全服务器并获得了良好的工具,但当我真的想了解发生了什么时,我会使用OpenSSL和Wireshark。我并不是说你应该在日常测试中使用OpenSSL;相反,你应该找到一个你信任的自动化工具。对于在线测试,我建议使用 Hardenize
;对于离线工作,可以考虑 testssl.sh
。但是当你真的需要确定某事时,唯一的办法就是使用OpenSSL。
最近,将OpenSSL用于测试目的变得更加困难,因为矛盾的是,OpenSSL本身变得更好了。在Heartbleed事件之后,OpenSSL开发人员进行了一次彻底的改革,其中一个方面是删除了过时的加密技术。当然,这对每个人来说都是个好消息,但确实让我们的生活变得更加困难。为了测试各种各样的条件,我们可能需要使用两个版本:一个是最新版本,一个是旧版本。最近的版本对于测试现代功能(例如TLS 1.3)很有用,但旧版本是测试过时功能所需要的。
在撰写本文时,新版本肯定来自1.1.1分支。至于旧版本,经过一些研究,我决定使用OpenSSL 1.0.2g,配置后可以恢复删除一些过时的功能:
xxxxxxxxxx
$ ./config \
--prefix=/opt/openssl-1.0.2g \
--openssldir=/opt/openssl-1.0.2g \
no-shared \
enable-ssl2 \
enable-ssl3 \
enable-weak-ssl-ciphers
在本章中,我将把这两个版本的OpenSSL称为 new(新版本)和 old(旧版本)。这就是你如何知道要使用哪个版本进行测试。有关如何配置和安装OpenSSL的更多信息,请参阅上一章。
OpenSSL附带了一个客户端工具,您可以使用它连接到安全的服务器。该工具类似于 telnet
或 nc
,因为它处理加密方面,但允许您完全控制接下来的层。
要连接到服务器,您需要提供主机名和端口。例如:
xxxxxxxxxx
$ openssl s_client -crlf \
-connect www.feistyduck.com:443 \
-servername www.feistyduck.com
请注意,您必须提供两次主机名。-connect
开关用于建立TCP连接,但 -servername
用于指定在TLS级别发送的主机名。从OpenSSL 1.1.1开始,s_client
工具会自动配置后者。如果(1)您使用的是早期版本的OpenSSL,(2)您连接到IP地址,或(3)TLS主机需要不同,您仍然需要使用 -servername
开关。使用 -noservername
开关避免在TLS握手中发送主机名信息。
键入命令后,您将看到大量诊断输出(稍后将详细介绍),然后有机会键入您想要的任何内容。因为我们正在与HTTP服务器交谈,所以最明智的做法是提交HTTP请求。在以下示例中,我使用 HEAD
请求,因为它指示服务器不要发送响应正文:
xxxxxxxxxx
HEAD / HTTP/1.0
Host: www.feistyduck.com
HTTP/1.1 200 OK
Date: Mon, 24 Aug 2020 16:38:02 GMT
Server: Apache
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Cache-control: no-cache, must-revalidate
Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked
Set-Cookie: JSESSIONID=882D48C8842EA82E3F3AFACC4425A695; Path=/; Secure; HttpOnly
Connection: close
read:errno=0
xxxxxxxxxx
备注
如果以这种方式连接到远程服务器时,TLS握手完成,但在第一行HTTP请求后断开连接,请检查您是否在命令行上指定了-crlf开关。此开关可确保您键入的换行符被转换为回车符加换行符组合,以确保字符串HTTP合规性。
现在我们知道TLS通信层正在工作:我们接通了HTTP服务器,提交了一个请求,并收到了回复。让我们回到诊断输出。前几行将显示有关服务器证书的信息:
xxxxxxxxxx
CONNECTED(00000003)
depth=2 C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN = COMODO RSA Certification Authority
verify return:1
depth=1 C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN = COMODO RSA Domain Validation Secure Server CA
verify return:1
depth=0 OU = Domain Control Validated, OU = PositiveSSL, CN = www.feistyduck.com
verify return:1
输出中的下一部分按交付顺序列出了服务器提供的所有证书:
xxxxxxxxxx
Certificate chain
0 s:OU = Domain Control Validated, OU = PositiveSSL, CN = www.feistyduck.com
i:C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN = COMODO RSA Domain Validation Secure Server CA
1 s:C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN = COMODO RSA Domain Validation Secure Server CA
i:C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN = COMODO RSA Certification Authority
对于每个证书,第一行显示主题,第二行显示颁发者信息。
当您需要查看发送的证书时,这部分非常有用;浏览器证书查看器通常显示重建的证书链,这些证书链几乎与呈现的证书链完全不同。要确定链在名义上是否正确,您可能希望验证主体和发行者是否匹配。您从顶部的叶子(web服务器)证书开始,然后沿着列表往下走,将当前证书的颁发者与下一个证书的主题进行匹配。您看到的最后一个颁发者可以指向不在链中的某个根证书,或者——如果包含自签名根证书——它可以指向自己。
输出中的下一项是服务器证书;这是很多文本,但为了简洁起见,我将删除其中的大部分:
xxxxxxxxxx
Server certificate
-----BEGIN CERTIFICATE-----
MIIFUzCCBDugAwIBAgIRAPR/CbWZEksfCIRqxNcesPIwDQYJKoZIhvcNAQELBQAw
gZAxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMTYwNAYD
[...]
L1MPjFiB5pyvf9jDBxv8TmG4Q6TnDDhw2t2Qil6lhsPAMZ9odP22W3uaLE1y7aB6
zbQXjVsc3E1THfFZWRzDPsU4fN/1iGlbrcAWa2sFfhJXrCDfAowFJ8A1n9jMiNEG
WfQfGgA2ar2xUtsqA7Re6XlXOlwBPuQ=
-----END CERTIFICATE-----
subject=OU = Domain Control Validated, OU = PositiveSSL, CN = www.feistyduck.com
issuer=C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN = COMODO RSA Domain Validation Secure Server CA
xxxxxxxxxx
备注
默认情况下,`s_client` 工具只显示叶子证书。如果您想获得整个链,请使用 `-showcerts` 开关。
如果你想更好地查看证书,你首先需要从输出中复制它并将其存储在一个单独的文件中。我将在下一节讨论如何做到这一点。
以下是关于TLS连接的大量信息,其中大部分是不言而喻的:
xxxxxxxxxx
---
No client certificate CA names sent
Peer signing digest: SHA512
Peer signature type: RSA
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 3624 bytes and written 446 bytes
Verification: OK
---
New, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES128-GCM-SHA256
Session-ID: 73FC4831AF053C46291C2D8CC90BF7F1D5B12178E488FBB4DC49A302B870E8DE
Session-ID-ctx:
Master-Key: E60DA9C6669C2C7DFFD8A3AD2CD17405CC0B9B69C4184469D779A9BA19A6FD4B3D602A023BD8B23F8D9A9FF2CBB5DDF7
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 300 (seconds)
TLS session ticket:
0000 - 31 95 ff d0 4c 42 dd d0-24 64 03 5c fc 55 1d 17 1...LB..$d.\.U..
0010 - 05 c4 61 1f b8 ba fd fe-f7 6c 6c e9 ae a2 49 3f ..a......ll...I?
0020 - c5 19 d4 e9 69 a5 79 d5-af 13 26 c8 2c e7 f0 01 ....i.y...&.,...
0030 - 3b 42 d8 c0 29 4c fa 7e-88 aa 8d c8 0b 30 96 ce ;B..)L.~.....0..
0040 - 43 40 2c 09 0b aa 2e d5-61 e3 34 7a a3 78 2f 93 C@,.....a.4z.x/.
0050 - 67 5a b9 96 78 f5 e7 69-b7 b6 2d 8c 00 8f 04 ab gZ..x..i..-.....
0060 - 42 1d 26 db 92 ec 2d 2f-ba 1c c6 61 87 64 0e d5 B.&...-/...a.d..
0070 - f2 ce 20 d0 07 a5 e2 6d-c6 45 50 c2 45 14 a8 ee .. ....m.EP.E...
0080 - 59 7c 63 e1 d7 d8 b0 b6-76 21 d2 13 97 eb bd 97 Y|c.....v!......
0090 - a1 d3 e8 5c 61 da da 2d-85 80 db ae de 56 97 e1 ...\a..-.....V..
00a0 - e8 7a 25 f9 bf cf b6 18-48 5b b0 03 a5 e6 ec 0a .z%.....H[......
00b0 - bf 2f 0d 1a 6b ae 79 10-80 9c cf 4d 66 8f 90 43 ./..k.y....Mf..C
00c0 - 69 54 32 be 0c 89 57 e8-6d 81 b5 3e 5b cb 5e 8e iT2...W.m..>[.^.
Start Time: 1598288068
Timeout : 7200 (sec)
Verify return code: 0 (ok)
Extended master secret: no
这里最重要的信息是协议版本(TLS 1.2)和使用的密码套件(ECDHE-RSA-AES128-GCM-SHA256)。请注意,协议信息出现在两个位置,当显示不同版本时,这可能会造成混淆。第一个位置描述了协商的密码套件的最低协议要求,而第二个位置指向当前正在协商的实际协议版本。您将看到一些旧密码套件的协议版本存在差异,例如:
xxxxxxxxxx
New, TLSv1/SSLv3, Cipher is DHE-RSA-AES128-SHA
所选套件可以与SSL 3.0一起使用,但在此连接上与TLS 1.2一起使用:
xxxxxxxxxx
Protocol : TLSv1.2
Cipher : DHE-RSA-AES128-SHAs
您还可以确定服务器已向您发出会话ID和TLS会话票证(一种在不让服务器保持状态的情况下恢复会话的方法),并且支持安全的重新协商。
xxxxxxxxxx
备注
如果您连接到TLS 1.3服务器,输出可能会有所不同。有时,您最初会观察到较少的信息,稍后会突然收到更多信息。此行为取决于实现,并反映了TLS 1.3中的更改,TLS 1.3将会话票证作为单独的协议消息传输,仅在握手完成后发送。此外,多个会话票证通常在同一连接上发送。
仅仅因为您能够连接到TLS服务器,并不意味着服务配置正确,即使服务器支持所有正确的协议和密码套件。配置的证书与正确的DNS名称匹配同样重要。
默认情况下, s_client
工具会报告但忽略证书问题。此外,在你开始相信它的判断之前,你需要相信它在看到有效证书时能够识别出来。当您使用自定义编译的二进制文件时,尤其如此。
在上一节的示例中,验证状态代码(显示在倒数第二行)为0,这意味着验证已成功。如果您正在连接到具有有效公共证书的服务器,但您看到的是状态20,这可能意味着受信任的根没有正确配置:
xxxxxxxxxx
Verify return code: 20 (unable to get local issuer certificate)
此时,如果你不想修复你的OpenSSL安装,你可以使用 -CApath
开关指向保存根目录的位置。例如:
xxxxxxxxxx
$ openssl s_client -connect www.feistyduck.com:443 \
-CApath /etc/ssl/certs/
如果您有一个包含根目录的单个文件,请使用 -CAfile
开关:
xxxxxxxxxx
$ openssl s_client -connect www.feistyduck.com:443 \
-CAfile /etc/ssl/certs/ca-certificates.crt
即使此时您获得了成功的状态代码,也不意味着证书配置正确。这是因为 s_client
工具不会检查给定主机名的证书是否正确;您必须告诉它手动执行此操作,并告诉它使用哪个主机名:
xxxxxxxxxx
$ openssl s_client -connect www.feistyduck.com:443 \
-verify_hostname www.feistyduck.com
如果不匹配,您可能会看到状态代码62:
xxxxxxxxxx
Verify return code: 62 (Hostname mismatch)
否则,您将看到熟悉的状态代码0。在极少数情况下,您需要验证为IP地址而不是主机名颁发的证书,您需要使用 -verify_IP
开关进行验证。
当与HTTP一起使用时,TLS会封装整个明文通信通道以形成HTTPS。其他一些协议从明文开始,但随后升级为加密。如果你想测试这样的协议,你必须告诉OpenSSL它是哪种协议,以便它可以代表你进行升级。使用 -starttls
开关提供协议信息。例如:
xxxxxxxxxx
$ openssl s_client -connect gmail-smtp-in.l.google.com:25 \
-starttls smtp
在撰写本文时,最新OpenSSL版本支持的协议是 smtp
、pop3
、imap
、ftp
、xmpp
、xmpp-server
、irc
、postgres
、mysql
、lmtp
、nntp
、sieve
和 ldap
。OpenSSL 1.0.2g的选择较少:smtp
、pop3
、imap
、ftp
和 xmpp
。
某些协议要求客户端提供其名称。例如,对于SMTP,默认情况下OpenSSL将使用 mail.example.com
,但您可以使用 -name
开关指定正确的值。如果您正在测试XMPP,可能需要指定正确的服务器名称;您可以使用 -xmpphost
开关来实现这一点。
当您使用 s_client
连接到远程安全服务器时,它会将服务器的PEMencoded证书转储到标准输出。如果出于任何原因需要证书,可以从回滚缓冲区复制它。如果您事先知道只想检索证书,则可以使用此命令行作为快捷方式:
xxxxxxxxxx
$ echo | openssl s_client -connect www.feistyduck.com:443 2>&1 | sed --quiet '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > feistyduck.crt
echo
命令一开始的目的是将shell与 s_client
分离。如果不这样做,s_client
将等待您的输入,直到服务器超时(这可能需要很长时间)。
默认情况下, s_client
只打印叶子证书;如果你想打印整个链,给它打 -showcerts
开关。启用该开关后,上一个命令行将把所有证书放在同一个文件中。
xxxxxxxxxx
$ echo | openssl s_client -showcerts -connect www.feistyduck.com:443 2>&1 | sed --quiet '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > feistyduck.chain
另一个有用的技巧是将 s_client
的输出直接传输到 x509
工具。以下命令显示了详细的服务器信息及其SHA256指纹:
xxxxxxxxxx
$ echo | openssl s_client -connect www.feistyduck.com:443 2>&1 | openssl x509 -noout -text -fingerprint -sha256
有时,您需要获取证书指纹并将其与其他工具一起使用。不幸的是,OpenSSL以显示单个字节并使用冒号分隔它们的格式输出证书。这个方便的命令行通过删除冒号并将十六进制字符转换为小写来规范证书指纹:
xxxxxxxxxx
$ echo | openssl s_client -connect www.feistyduck.com:443 2>&1 | openssl x509 -noout -fingerprint -sha256 | sed 's/://g' | tr '[:upper:]' '[:lower:]' | sed 's/sha256 fingerprint=//g'
xxxxxxxxxx
备注
连接到远程TLS服务器并查看其证书是一项非常常见的操作,但您不应该花时间记住和键入这些长命令。相反,投资编写几个shell函数,将此功能打包成易于使用的命令。
默认情况下,s_client
将尝试使用最佳协议与远程服务器通信,并在输出中报告协商的版本。如前所述,您将在输出中两次找到协议版本,并且您需要明确谈论协议的行:
xxxxxxxxxx
Protocol : TLSv1.2
如果您需要测试对特定协议版本的支持,您有两个选择。您可以通过提供 -ssl2
、-ssl3
、-tls1
、-tls_1
、-tls_2
或 tls1_3
开关之一来显式选择一个协议进行测试。当然,每个开关都需要在测试工具中支持特定的协议版本。如果你想从测试中排除特定的协议,有一系列开关可以禁用协议(例如,TLS 1.2的 -no_tls_1_2
)。有时,这可能是更好的方法。从OpenSSL 1.1.0开始,有两个新选项,-min_protocol
和 -max-protocol
,分别控制最低和最高协议版本。
例如,在测试不支持特定协议版本的服务器时,您可能会得到以下输出:
xxxxxxxxxx
$ openssl s_client -connect www.example.com:443 -tls1_2
CONNECTED(00000003)
140455015261856:error:1408F10B:SSL routines:SSL3_GET_RECORD:wrong version number:s3_pkt.c:340:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 5 bytes and written 7 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1.2
Cipher : 0000
Session-ID:
Session-ID-ctx:
Master-Key:
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
Start Time: 1339231204
Timeout : 7200 (sec)
Verify return code: 0 (ok)
---
了解服务器是否支持SSL 2.0有时可能需要更多的工作,因为这个旧的、非常不安全的SSL协议版本使用的握手与SSL 3.0以后使用的握手不同。虽然现在只支持SSL 2.0的服务器应该非常罕见,但要检查这种可能性,您需要使用 -ssl2
开关提交一个单独的检查。
另一个协议差异是,SSL 2.0服务器有时没有配置任何密码套件。在这种情况下,虽然支持SSL 2.0,但从技术上讲,任何握手尝试都会失败。您应该将这种情况视为配置错误。
您不太可能花费大量时间在命令行上使用OpenSSL测试密码套件配置。这是因为一次只能有效地测试一个套件;对TLS 1.2和早期协议修订版支持的300多个密码套件进行测试将需要相当长的时间。这是一个使用这些方便的工具来自动化流程的绝佳机会。
不过,有时您需要探测服务器,以确定它们是否支持特定的套件或加密原语,或者首选项是否配置正确。
TLS 1.3的引入使该领域的测试稍微复杂一些,但仍然可以管理。由于此协议版本与所有其他版本之间的差异,通常最好将测试分为两组。在测试TLS 1.3时,请始终将 -cryptsuites
开关与 -tls1_3
结合使用。通常的方法是只指定一个套件来确定它是否受支持:
xxxxxxxxxx
$ echo | openssl s_client -connect www.hardenize.com:443 -tls1_3 -ciphersuites TLS_AES_128_GCM_SHA256 2>/dev/null| grep New
New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256
如果选择不支持的套件,输出自然会有所不同:
xxxxxxxxxx
$ echo | openssl s_client -connect www.hardenize.com:443 -tls1_3 -ciphersuites TLS_AES_128_CCM_SHA256 2>/dev/null | grep New
New, (NONE), Cipher is (NONE)
当您测试TLS 1.2和早期协议版本的配置时,请将 -crepher
开关与 -no-tls1_3
结合使用(假设您使用的OpenSSL版本支持TLS 1.3):
xxxxxxxxxx
$ echo | openssl s_client -connect www.hardenize.com:443 -no_tls1_3 -cipher AESGCM 2>/dev/null | grep New
New, TLSv1.2, Cipher is ECDHE-ECDSA-AES128-GCM-SHA256
正如您在前面的示例中看到的,在测试TLS 1.2及更早版本时,您不必只指定一个密码套件,但在这种情况下,您需要观察协商的内容。如果你想进一步探索,你总是可以调整命令行以删除之前协商过的套件:
xxxxxxxxxx
$ echo | openssl s_client -connect www.hardenize.com:443 -no_tls1_3 -cipher 'AESGCM:!ECDHE-ECDSA-AES128-GCM-SHA256' 2>/dev/null | grep New
New, TLSv1.2, Cipher is ECDHE-ECDSA-AES256-GCM-SHA384
即使您不会手动测试大量套件,也有一种快速的方法可以确定特定服务器是否支持许多糟糕的加密原语中的任何一个。为此,请使用旧的OpenSSL版本并列出所有错误的密码套件关键字,如下所示:
xxxxxxxxxx
$ echo | openssl s_client -connect example.com:443 -cipher '3DES DES RC2 RC4 IDEA SEED CAMELLIA MD5 aNULL eNULL EXPORT LOW' 2>/dev/null | grep New
New, TLSv1/SSLv3, Cipher is DHE-RSA-CAMELLIA256-SHA
另一个很好的测试是查看服务器是否支持不支持前向保密的RSA密钥交换:
xxxxxxxxxx
$ echo | /opt/openssl-1.0.2g/bin/openssl s_client -connect example.com:443 -cipher kRSA 2>/dev/null | grep New
New, TLSv1/SSLv3, Cipher is AES128-GCM-SHA256
理想情况下,你会在这里遇到握手失败,但如果你没有,这并不可怕,只要服务器只将RSA密钥交换作为最后手段。您可以通过提供具有前向保密功能的套房作为您最不喜欢的选择来检查这一点:
xxxxxxxxxx
$ echo | /opt/openssl-1.0.2g/bin/openssl s_client -connect example.com:443 -cipher 'DHE ECDHE kRSA +kECDHE +kDHE' 2>/dev/null | grep New
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
作为一般规则,TLS服务器应始终配置为强制执行其密码套件偏好,确保它们与每个客户端协商其首选密码套件。此功能对于TLS 1.2和更早的协议修订版至关重要,这些修订版支持许多密码套件,其中大多数都是不理想的。TLS 1.3的情况则不同:目前只有少数套件可用,而且所有套件都是安全的,因此强制服务器偏好并不重要。
xxxxxxxxxx
备注
因为密码套件偏好对TLS 1.3来说并不重要,所以一些堆栈甚至不支持此协议,即使它们支持早期的协议版本。因此,为了获得最佳结果,您需要分别测试TLS 1.3和其他所有协议,或者分别测试每个支持的协议。这是自动化是更好选择的另一种情况。
要测试服务器套件偏好,您首先需要了解支持哪些套件。例如,您可以拥有支持的套件的完整列表。或者,您可以使用不同的套件类型来探测服务器,例如,使用ECDHE与DHE或RSA密钥交换的套件。
手头有两个套件,您需要启动两个连接,首先提供其中一个套件作为您的首选,然后提供另一个:
xxxxxxxxxx
$ echo | openssl s_client -connect www.hardenize.com:443 -tls1_3 -ciphersuites 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384' 2>/dev/null | grep New
New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256
$ echo | openssl s_client -connect www.hardenize.com:443 -tls1_3 -ciphersuites 'TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256' 2>/dev/null | grep New
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
如果您看到在两个连接上协商了相同的套件,这意味着服务器被配置为主动选择协商的套件。否则,事实并非如此。上例中的服务器是不强制执行首选项的TLS 1.3服务器之一。同一台服务器确实首选TLS 1.2;我们可以看到,它总是选择一个更好的套件,即使我们把它推到列表的末尾:
xxxxxxxxxx
$ echo | openssl s_client -connect www.hardenize.com:443 -no_tls1_3 -cipher 'ECDHE+AESGCM RSA' 2>/dev/null | grep New
New, TLSv1.2, Cipher is ECDHE-ECDSA-AES128-GCM-SHA256
$ echo | openssl s_client -connect www.hardenize.com:443 -no_tls1_3 -cipher 'ECDHE+AESGCM RSA +ECDHE' 2>/dev/null | grep New
New, TLSv1.2, Cipher is ECDHE-ECDSA-AES128-GCM-SHA256
当涉及到服务器套件偏好测试时,最好避免使用ChaCha20套件。这是因为一些服务器支持另一种类型的偏好,在这种偏好中,它们在安全性方面将AES-GCM和ChaCha20套件视为平等的,并将客户端偏好视为特殊情况。
其想法是,客户端将更喜欢更快的密码套件,通常是用于移动设备的ChaCha20和用于台式机的AES-GCM。也就是说,对于支持这种偏好的服务器,您可能需要测试它是否正常工作。为此,您需要使用三个受支持的密码套件和三个测试。前两个测试的目的是确定服务器在不涉及ChaCha20时选择其最喜欢的套件:
xxxxxxxxxx
$ echo | openssl s_client -connect www.hardenize.com:443 -no_tls1_3 -cipher 'ECDHE-RSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-CHACHA20-POLY1305' 2>/dev/null | grep New
New, TLSv1.2, Cipher is ECDHE-ECDSA-AES128-GCM-SHA256
$ echo | openssl s_client -connect www.hardenize.com:443 -no_tls1_3 -cipher 'ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-ECDSA-CHACHA20-POLY1305' 2>/dev/null | grep New
New, TLSv1.2, Cipher is ECDHE-ECDSA-AES128-GCM-SHA256
如果你看到服务器在这两种情况下都使用相同的套件进行响应,你可以先使用支持的ChaCha20套件提交另一个测试。如果你看到服务器选择它,你就知道它被配置为支持客户端首选套件:
xxxxxxxxxx
$ echo | openssl s_client -connect www.hardenize.com:443 -no_tls1_3 -cipher 'ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES128-GCM-SHA256' 2>/dev/null | grep New
New, TLSv1.2, Cipher is ECDHE-ECDSA-CHACHA20-POLY1305
命名组(named groups)是用于密钥交换的预定义加密参数。在TLS 1.3中,命名组包括椭圆曲线(elliptic curve,EC)和有限域(finite field,DH)参数。TLS 1.2和更早版本通常只使用预定义的椭圆曲线;服务器在每个连接上提供DH参数。在握手过程中,客户端和服务器必须就一个共同命名的组达成一致,密钥交换将在该组上进行,所选组满足所需的安全要求非常重要。
在实践中,很少需要为命名组测试服务器。尽管在各种RFC中有相当多的命名组,但OpenSSL可能是唯一得到广泛支持的主要客户端。从历史上看,你只能使用NIST的P-256和P-384 EC组,因为这些是唯一得到广泛支持的曲线。最近,X25519和X448组被添加为替代品。因为所有这些曲线都很强,所以几乎不需要花时间去思考它们。
您可能会发现自己通常会测试命名组配置,以了解您的web服务器正在做什么。例如,您可能关心X25519,并希望确保它可用且首选。要测试这一点,请使用 s_client
工具和 -curves
开关。例如,以下是如何确定是否支持单个命名组:
xxxxxxxxxx
$ echo | openssl s_client -connect hardenize.com:443 -curves X25519 2>/dev/null | grep "Server Temp Key"
Server Temp Key: X25519, 253 bits
成功后,您将在输出中看到指定的组,因为这是为握手选择的组。失败时,您可能看不到输出,这意味着握手失败。或者,无法协商ECDHE套件的服务器可能会退回到DHE套件,如以下输出所示:
xxxxxxxxxx
Server Temp Key: DH, 2048 bits
如果你需要测试命名组偏好,你需要提供两个或多个命名组,最后一个是你喜欢的。如果你看到它经过协商,这意味着服务器会主动选择它认为最合适的组。使用冒号分隔组,并注意名称区分大小写。
xxxxxxxxxx
$ echo | openssl s_client -connect hardenize.com:443 -curves prime256v1:X25519 2>/dev/null | grep "Server Temp Key"
Server Temp Key: X25519, 253 bits
xxxxxxxxxx
备注
您可以使用 `ecparam` 工具和 `-list_curves` 开关获得OpenSSL支持的椭圆曲线的完整列表。在该列表中添加X25519和X448。目前还不支持有限字段组,但OpenSSL 3.0应该支持。
基于DNS的命名实体身份验证(DNS-based Authentication of Named Entities,DANE)是一组标准,使您能够通过DNS配置认可您使用的TLS证书。为了实现这一点,DANE要求DNS本身是安全的,这意味着DNSSEC是必要的。因此,DANE本质上是一种钉扎机制(a mechanism for pinning);只有您批准的证书才会被启用DANE的客户端视为有效。DANE本身没有争议,但它所依赖的DNSSEC是一个非常分裂的话题,世界在爱它和恨它之间分裂。因此,DANE目前没有得到普遍支持。它更常用于保护SMTP服务器;浏览器级别没有支持。
由于DNS配置的传播和缓存方式,支持DANE会增加TLS部署的复杂性。在使用新证书之前,您需要确保您的新DNS配置(认可该证书)已完全传播。因此,您通常会首先发布DNS更改,等待足够的时间来清除缓存,然后才部署证书。
测试本身很简单;在向 s_client
工具提供DANE数据时使用它。这很方便,因为它使您甚至可以在更改DNS之前测试连接。首先,让我们看看DANE配置是什么样子的。
DANE将配置存储在TLSA资源记录中,使用两个前缀标签来指示协议和端口:
xxxxxxxxxx
$ host -t TLSA _25._tcp.mail.protonmail.ch
_25._tcp.mail.protonmail.ch has TLSA record 3 1 1 76BB66711DA416433CA890A5B2E5A0533C6006478F7D10A4469A947A CC8399E1
_25._tcp.mail.protonmail.ch has TLSA record 3 1 1 6111A5698D23C89E09C36FF833C1487EDC1B0C841F87C49DAE8F7A09 E11E979E
此输出包含两个代言(endorsements),每个证书一个。有两个代言并不罕见。例如,您可能有一个使用两个证书的服务(例如,一个使用RSA密钥,另一个使用ECDSA密钥),或者您有一个备份证书,或者您只是在切换证书时处于过渡期。开头的三个数字表示代言通过其公钥(1)和SHA256哈希(1)直接针对证书(3)。其余的数据是哈希值本身。
为了测试,您在使用 -dane_tlsa_domain
和 -dane_tlsa_rrdata
开关提供DANE数据的同时连接到SMTP服务:
xxxxxxxxxx
$ openssl s_client -starttls smtp \
-connect mail.protonmail.ch:25 \
-dane_tlsa_domain mail.protonmail.ch \
-dane_tlsa_rrdata "3 1 1 76BB66711DA416433CA890A5B2E5A0533C6006478F7D10A4469A947ACC8399E1"
如果验证成功,您将在输出中看到类似这样的内容:
xxxxxxxxxx
---
SSL handshake has read 5209 bytes and written 433 bytes
Verification: OK
Verified peername: *.protonmail.ch
DANE TLSA 3 1 1 ...8f7d10a4469a947acc8399e1 matched EE certificate at depth 0
---
如果你想测试验证失败,只需破坏提供的哈希值。结果将类似于以下输出:
xxxxxxxxxx
---
SSL handshake has read 5209 bytes and written 433 bytes
Verification error: No matching DANE TLSA records
---
为了获得最佳结果,当以这种方式测试DANE时,始终提供所有已知的TLSA记录(每个 -dane_tlsa_rrdata
开关一个)。如果您这样做,无论协商什么证书,同时使用多个证书的服务都将签出。对于TLS 1.2及更早版本,可以通过选择客户端支持的密码套件( -chipher
开关)强制使用特定证书。TLS 1.3套件不同,对于此协议版本,您需要使用 -sigalgs
开关,其值为 ecdsa_secp256r1_sha256
或 rsa_ps_rsae_sha256
。
当与 -reconnect
开关结合使用时,s_client
命令可用于测试会话重用。在此模式下, s_client
将连接到目标服务器六次。它将在第一个连接上创建一个新会话,然后尝试在后续的五个连接中重用同一会话:
xxxxxxxxxx
$ echo | openssl s_client -connect www.feistyduck.com:443 -reconnect
xxxxxxxxxx
备注
由于OpenSSL中的一个错误,在撰写本文时,会话恢复测试无法与TLS 1.3结合使用。在bug得到解决之前,你能做的最好的事情就是测试早期的协议版本。使用 `-no-tls1_3` 开关。
前面的命令将产生大量输出,其中大部分您不会关心。关键部分是关于新会话和重用会话的信息。开始时应该只有一次新的会议,如下行所示:
xxxxxxxxxx
New, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
接下来是五次会话重用,用这样的行表示:
xxxxxxxxxx
Reused, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
大多数时候,您不想查看所有输出,而是希望快速得到答案。你可以使用以下命令行获取它:
x
$ echo | openssl s_client -connect www.feistyduck.com:443 -reconnect 2> /dev/null | grep 'New\|Reuse'
New, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Reused, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Reused, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Reused, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Reused, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Reused, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
以下是该命令的作用:
-reconnect
开关激活会话重用模式。2> /dev/null
部分隐藏了您不关心的 stderr
输出。grep
过滤掉其余的绒毛,只允许您关心的行通过。xxxxxxxxxx
备注
如果您不想在测试中包含会话票证,例如,因为并非所有客户端都支持此功能,您可以使用 `-no-ticket` 开关禁用此恢复方法。此选项不适用于TLS 1.3。
如果您需要更好地控制恢复,s_client
工具提供了将连接状态持久化到文件的选项。在第一次连接时,使用 -sess_out
开关记录状态:
xxxxxxxxxx
$ openssl s_client -connect www.feistyduck.com:443 -sess_out sess.pem
要查看记录的状态,请使用 sess_id
工具:
xxxxxxxxxx
$ openssl sess_id -in sess.pem -noout -text
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-ECDSA-AES128-GCM-SHA256
Session-ID: F7384C2C4BE621F66045ECE12A89821FEE789C2E75B78C90C428BE37E0FE4599
Session-ID-ctx:
Master-Key: 9D39C582D9AA1618B2F16C7911C4BFFB61D6D1FD578A93B1145FD2B4DBFDE76EB22
79BA50AEFFCD95320BEEBC9489FAF
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 64800 (seconds)
TLS session ticket:
0000 - a2 d3 e3 04 03 21 85 6d-1a 4f 9c 82 fc 4e 15 e0 .....!.m.O...N..
0010 - 9b b8 b1 24 0d 95 a3 0a-b8 24 d4 f5 d2 be b8 56 ...$.....$.....V
0020 - b2 f0 e9 c5 e5 53 31 b5-24 74 96 ba e4 56 32 68 .....S1.$t...V2h
0030 - fe bb 7a 7f 28 d7 c4 19-6a c5 ca 22 3a a7 2d 45 ..z.(...j..":.-E
0040 - 52 91 74 f7 a8 fa 75 40-02 b9 84 9c 84 0d a8 06 R.t...u@........
0050 - c7 a1 65 af 8b 54 19 74-52 e8 c4 f4 47 1c 3f f0 ..e..T.tR...G.?.
0060 - 46 35 1a 3c a9 a5 73 30-33 b7 20 bd dc 8a b8 f9 F5.<..s03. .....
0070 - 79 20 4a de b3 60 83 53-c7 a7 62 e1 a2 9e 55 8c y J..`.S..b...U.
0080 - 24 0a f5 4c ab 81 a5 d9-36 ae 52 61 a1 4e b7 99 $..L....6.Ra.N..
0090 - 20 9e ca 67 49 ea 80 a4-14 ce ac 36 aa 20 0e 53 ..gI......6. .S
00a0 - d7 9f 14 a6 7c b9 88 4c-6b 69 93 d4 62 fb 02 50 ....|..Lki..b..P
Start Time: 1602414785
Timeout : 300 (sec)
Verify return code: 20 (unable to get local issuer certificate)
Extended master secret: no
最后,要使用相同的会话状态再次连接,请使用 -sess_in
开关:
xxxxxxxxxx
$ openssl s_client -connect www.feistyduck.com:443 -sess_in sess.pem
以这种方式保持连接之间的状态可以让您更好地控制,并使您能够将连接参数从一个连接完全更改为另一个连接。例如,您可以在第一次尝试时连接到一台服务器,然后在第二次尝试时再连接到另一台服务器。当您需要测试web服务器集群上是否正确实现了会话恢复时,这可能会有用。手动控制连接允许您将它们分散到一段时间内,也许可以测试会话超时和票证密钥轮换。
如果OCSP响应器出现故障,有时很难准确理解原因。从命令行检查证书吊销状态是可能的,但并不十分简单。您需要执行以下步骤:
对于前两个步骤,使用指定的 -showcerts
开关连接到服务器:
xxxxxxxxxx
$ openssl s_client -connect www.feistyduck.com:443 -showcerts
输出中的第一个证书将是属于服务器的证书。如果证书链配置正确,则第二个证书将是颁发者的证书。要确认,请检查第一个证书的颁发者是否与第二个证书的主题匹配:
x
Certificate chain
0 s:OU = Domain Control Validated, OU = PositiveSSL, CN = www.feistyduck.com
i:C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN = COMODO RSA Domain Validation Secure Server CA
-----BEGIN CERTIFICATE-----
MIIFUzCCBDugAwIBAgIRAPR/CbWZEksfCIRqxNcesPIwDQYJKoZIhvcNAQELBQAw
[...]
zbQXjVsc3E1THfFZWRzDPsU4fN/1iGlbrcAWa2sFfhJXrCDfAowFJ8A1n9jMiNEG
WfQfGgA2ar2xUtsqA7Re6XlXOlwBPuQ=
-----END CERTIFICATE-----
1 s:C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN = COMODO RSA Domain Validation Secure Server CA
i:C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN = COMODO RSA Certification Authority
-----BEGIN CERTIFICATE-----
MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB
hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
[...]
如果第二个证书不正确,请检查链的其余部分;有些服务器没有按照正确的顺序为链提供服务。如果在链中找不到颁发者证书,则必须在其他地方找到它。一种方法是在叶子证书中查找权威信息访问(Authority Information Access)扩展:
x
$ openssl x509 -in fd.crt -noout -text
[...]
Authority Information Access:
CA Issuers - URI:http://crt.comodoca.com/COMODORSADomainValidationSecureServerCA.crt
OCSP - URI:http://ocsp.comodoca.com
如果存在CA颁发者(CA Issuers)信息,则应包含颁发者证书的URL。如果颁发者证书信息不可用,您可以尝试在浏览器中打开该网站,让它重建链,并从其证书查看器下载颁发证书。如果所有这些都失败了,您可以在信任存储中查找证书或访问CA的网站。
如果您已经拥有证书,只需要知道OCSP响应程序的地址,请使用 -ocsp_uri
开关和 x509
命令作为快捷方式:
xxxxxxxxxx
$ openssl x509 -in fd.crt -noout -ocsp_uri
http://ocsp.comodoca.com
现在,您可以提交OCSP请求:
x
$ openssl ocsp -issuer issuer.crt -cert fd.crt -url http://ocsp.comodoca.com -CAfile issuer.crt
WARNING: no nonce in response
Response verify OK
fd.crt: good
This Update: Aug 30 22:35:12 2020 GMT
Next Update: Sep 6 22:35:12 2020 GMT
你想在回复中寻找两件事。首先,检查响应本身是否有效(上例中的 Response verify OK
),其次,检查响应所说的内容。当您看到状态良好时,这意味着证书尚未被吊销。已吊销证书的状态将被吊销。
x
备注
关于丢失随机数的警告消息告诉您,OpenSSL希望使用随机数来防止重放攻击,但相关服务器没有回复。这通常是因为CA希望提高其OCSP响应者的性能。当它们禁用随机数保护(标准允许)时,OCSP响应可以生成(通常是批处理)、缓存并在一段时间内重用。
您可能会遇到对上一个命令行没有成功响应的OCSP响应程序。在这种情况下,以下建议可能会有所帮助。
不要请求nonce
一些服务器无法处理nonce请求并返回错误。默认情况下,OpenSSL将请求一个nonce。要禁用nonce,请使用 -no_nonce
命令行开关。
提供主机请求标头
虽然大多数OCSP服务器响应的HTTP请求在Host标头中没有指定正确的主机名,但有些服务器没有。如果您遇到包含HTTP错误代码的错误消息(例如404),请尝试将主机名添加到OCSP请求中。您可以在 -header
开关的帮助下完成此操作。
考虑到前两点,最终使用的命令如下:
x
$ openssl ocsp -issuer issuer.crt -cert fd.crt -url http://ocsp.comodoca.com -CAfile issuer.crt -no_nonce -header Host ocsp.comodoca.com
OCSP装订(stapling)是一个可选功能,允许服务器证书附带证明其有效性的OCSP响应。因为OCSP响应是通过已有的连接传递的,所以客户端不必单独获取它。
OCSP装订仅在客户端请求时使用,客户端在握手请求中提交 status_request
扩展。支持OCSP装订的服务器将通过在握手过程中包含OCSP响应来做出响应。
使用s_client工具时,通过 -status
开关请求OCSP装订:
xxxxxxxxxx
$ echo | openssl s_client -connect www.feistyduck.com:443 -status
OCSP相关信息将显示在连接输出的最开始。例如,对于不支持装订的服务器,您将在输出顶部附近看到这行:
xxxxxxxxxx
CONNECTED(00000003)
OCSP response: no response sent
使用支持装订的服务器,您将在输出中看到整个OCSP响应:
xxxxxxxxxx
OCSP Response Data:
OCSP Response Status: successful (0x0)
Response Type: Basic OCSP Response
Version: 1 (0x0)
Responder Id: 90AF6A3A945A0BD890EA125673DF43B43A28DAE7
Produced At: Aug 30 22:35:12 2020 GMT
Responses:
Certificate ID:
Hash Algorithm: sha1
Issuer Name Hash: 7AE13EE8A0C42A2CB428CBE7A605461940E2A1E9
Issuer Key Hash: 90AF6A3A945A0BD890EA125673DF43B43A28DAE7
Serial Number: F47F09B599124B1F08846AC4D71EB0F2
Cert Status: good
This Update: Aug 30 22:35:12 2020 GMT
Next Update: Sep 6 22:35:12 2020 GMT
Signature Algorithm: sha256WithRSAEncryption
1b:9d:be:3e:e6:b2:9a:e6:22:fe:69:cc:55:a9:62:5d:29:79:
[...]
证书状态良好意味着证书尚未被吊销。
使用证书吊销列表(Certificate Revocation List,CRL)检查证书验证比通过OCSP进行检查更复杂。流程如下:
第一步与OCSP检查重叠;要完成这些操作,请按照“检查OCSP撤销”一节中的说明进行操作。
x
$ openssl x509 -in fd.crt -noout -text | grep -A 5 CRL
[...]
URI:http://crl.comodoca.com/COMODORSADomainValidationSecureServerCA.crl
然后从CA获取CRL:
x
$ wget http://crl.comodoca.com/COMODORSADomainValidationSecureServerCA.crl -O
comodo.crl
验证CRL是否有效(即由颁发者证书签名):
xxxxxxxxxx
$ openssl crl -in comodo.crl -inform DER -CAfile issuer.crt -noout
verify OK
现在,确定要检查的证书的序列号:
xxxxxxxxxx
$ openssl x509 -in fd.crt -noout -serial
serial=F47F09B599124B1F08846AC4D71EB0F2
此时,您可以将CRL转换为人类可读的格式并手动检查:
x
$ openssl crl -in comodo.crl -inform DER -text -noout
Certificate Revocation List (CRL):
Version 2 (0x1)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN = COMODO RSA Domain Validation Secure Server CA
Last Update: Aug 31 07:52:03 2020 GMT
Next Update: Sep 7 07:52:03 2020 GMT
CRL extensions:
X509v3 Authority Key Identifier:
keyid:90:AF:6A:3A:94:5A:0B:D8:90:EA:12:56:73:DF:43:B4:3A:28:DA:E7
X509v3 CRL Number:
2149
1.3.6.1.4.1.311.21.4:
200903195203Z .
Revoked Certificates:
Serial Number: 70DAB4B3229280F04364BC58DB2AB922
Revocation Date: May 29 12:18:27 2017 GMT
Serial Number: 51894D40389CDAB84A7A6F3374E1D893
Revocation Date: May 30 23:20:55 2017 GMT
[...]
Signature Algorithm: sha256WithRSAEncryption
5a:7c:6e:6e:98:05:c4:24:2b:84:7a:28:6f:45:26:33:6b:88:
4d:dd:61:22:e4:23:47:76:c7:8a:55:ec:f9:72:29:47:21:73:
[...]
CRL以一些元数据开始,后面是一个已吊销证书的列表,最后以签名结束(我们在上一步中验证了签名)。如果服务器证书的序列号在列表中,则意味着它已被吊销。
如果你不想直观地查找序列号(有些crl可能很长),请使用 grep
查找它,但要注意你的格式与 crl
工具使用的格式匹配。例如:
x
$ openssl crl -in comodo.crl -inform DER -text -noout | grep F47F09B599124B1F08846AC4D71EB0F2
在TLS中,重新协商是一个失败的功能,它导致了几个协议弱点,其中一些很容易被利用。TLS 1.3不再支持重新协商,但仍有较旧的服务器支持早期的协议修订版。
s_client
工具有几个功能可以帮助您手动测试重新协商。首先,当您连接时,该工具将报告远程服务器是否支持安全重新协商。这是因为支持安全重新协商的服务器通过在握手阶段交换的特殊TLS扩展来表示对它的支持。当支持可用时,输出可能如下:
xxxxxxxxxx
New, TLSv1/SSLv3, Cipher is AES256-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
[...]
如果不支持安全重新协商,输出将略有不同:
xxxxxxxxxx
Secure Renegotiation IS NOT supported
xxxxxxxxxx
备注
由于TLS 1.3不支持重新协商,如果协商了此协议版本,`s_client` 工具将始终给出否定答案。为了确保可靠的结果,使用 `-no_tls1_3` 开关强制协商较早的协议版本。
即使服务器表示支持安全重新协商,您也可能希望测试它是否也允许客户端启动重新协商。客户端发起的重新协商是一种协议功能,在实践中没有任何作用(因为服务器在需要时总是可以发起重新协商),并使服务器更容易受到拒绝服务攻击。
要启动重新协商,在TLS握手完成后,单独在一行上键入R字符。例如,假设我们正在与HTTP服务器通信,您可以键入请求的第一行,启动重新协商,然后完成请求。以下是与支持客户端发起的重新协商的web服务器交谈时的情况:
x
GET / HTTP/1.0
R
RENEGOTIATING
depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert High Assurance EV Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 Extended Validation Server CA
verify return:1
depth=0 businessCategory = Private Organization, jurisdictionC = US, jurisdictionST = California, serialNumber = C2543436, C = US, ST = California, L = Mountain View, O = Mozilla Foundation, OU = Cloud Services, CN = addons.mozilla.org
verify return:1
Host: addons.mozilla.org
HTTP/1.1 301 Moved Permanently
Content-Type: text/plain; charset=utf-8
Date: Mon, 31 Aug 2020 12:40:49 GMT
Location: /en-US/firefox/
Strict-Transport-Security: max-age=31536000
Content-Length: 49
Connection: Close
Moved Permanently. Redirecting to /en-US/firefox/closed
当重新协商发生时,服务器将再次将其证书发送给客户端。您可以在输出中看到证书链的验证。之后的下一行继续显示Host请求标头。看到web服务器的响应就是支持重新协商的证明。由于不同版本的SSL/TLS库中解决重新协商问题的方式不同,不支持重新协商的服务器可能会中断连接,或者可能会保持连接打开但拒绝继续讨论(这通常会导致超时)。
不支持重新协商的服务器将断然拒绝连接上的第二次握手:
x
HEAD / HTTP/1.0
R
RENEGOTIATING
140003560109728:error:1409E0E5:SSL routines:SSL3_WRITE_BYTES:ssl handshake failure:s3_pkt.c:592:
在撰写本文时,OpenSSL的默认行为是连接到不支持安全重新协商的服务器;它还将接受安全和不安全的重新协商,选择服务器能够做的任何事情。如果重新协商在不支持安全重新协商的服务器上成功,您将知道服务器支持不安全的客户端发起的重新协商。
xxxxxxxxxx
备注
测试不安全重新协商的最可靠方法是使用本节中描述的方法,但使用在发现不安全重新谈判之前发布的OpenSSL版本(例如0.9.8k)。我提到这一点是因为有少数服务器支持安全和不安全的重新协商。使用现代版本的OpenSSL很难检测到此漏洞,因为OpenSSL总是更喜欢安全选项。
您可以使用OpenSSL或为此目的设计的工具之一手动测试Heartbleed。现在有许多实用程序可用,因为Heartbleed很容易被利用。但是,像往常一样,这些工具的准确性存在问题。有证据表明,一些工具无法检测到易受攻击的服务器。鉴于Heartbleed的严重性,最好手动测试或使用能够让您完全了解该过程的工具进行测试。我将介绍一种仅适用于OpenSSL修改版本的方法。
假设您的版本支持心跳协议(从1.0.1开始,但在1.1.0之前),测试的某些部分不需要对OpenSSL进行修改。例如,要确定远程服务器是否支持心跳协议,请在连接时使用 -tlsextdebug
开关显示服务器扩展:
xxxxxxxxxx
$ openssl s_client -connect www.feistyduck.com:443 -tlsextdebug
CONNECTED(00000003)
TLS server extension "renegotiation info" (id=65281), len=1
0001 - <SPACES/NULS>
TLS server extension "EC point formats" (id=11), len=4
0000 - 03 00 01 02 ....
TLS server extension "session ticket" (id=35), len=0
TLS server extension "heartbeat" (id=15), len=1
0000 - 01
[...]
不返回心跳扩展的服务器不易受Heartbleed攻击。要测试服务器是否响应心跳请求,请使用-msg开关请求显示协议消息,连接到服务器,等待握手完成,然后键入B 并按return :
xxxxxxxxxx
$ openssl s_client -connect www.feistyduck.com:443 -tlsextdebug -msg
[...]
---
B
HEARTBEATING
>>> TLS 1.2 [length 0025], HeartbeatRequest
01 00 12 00 00 3c 83 1a 9f 1a 5c 84 aa 86 9e 20
c7 a2 ac d7 6f f0 c9 63 9b d5 85 bf 9a 47 61 27
d5 22 4c 70 75
<<< TLS 1.2 [length 0025], HeartbeatResponse
02 00 12 00 00 3c 83 1a 9f 1a 5c 84 aa 86 9e 20
c7 a2 ac d7 6f 52 4c ee b3 d8 a1 75 9a 6b bd 74
f8 60 32 99 1c
read R BLOCK
此输出显示完整的心跳请求和响应。两个心跳消息中的第二个和第三个字节指定了有效载荷长度。我们提交了18个字节(12个十六进制)的有效载荷,服务器以相同大小的有效载荷进行了响应。在这两种情况下,还有额外的16个字节的填充。有效载荷中的前两个字节构成序列号,OpenSSL使用该序列号来匹配对请求的响应。剩余的有效载荷字节和填充只是随机数据。
要检测易受攻击的服务器,您必须准备一个特殊版本的OpenSSL,该版本发送的有效载荷长度不正确。易受攻击的服务器采用声明的有效载荷长度,并以如此多的字节进行响应,而不管实际提供的有效载荷的长度如何。
此时,您必须决定是要构建侵入性测试(通过从流程中检索一些数据来利用服务器)还是非侵入性测试。这将取决于你的情况。如果您有测试活动的许可,请使用侵入性测试。有了它,您将能够准确地看到返回的内容,并且不会有错误的空间。例如,某些版本的GnuTLS支持Heartbeat,并会以不正确的有效载荷长度响应请求,但它们实际上不会返回服务器数据。非侵入性检测不能可靠地诊断这种情况。
以下针对OpenSSL 1.0.1h的补丁创建了该测试的非侵入性版本:
xxxxxxxxxx
--- t1_lib.c.original 2014-07-04 17:29:35.092000000 +0100
+++ t1_lib.c 2014-07-04 17:31:44.528000000 +0100
@@ -2583,6 +2583,7 @@
#endif
#ifndef OPENSSL_NO_HEARTBEATS
+#define PAYLOAD_EXTRA 16
int
tls1_process_heartbeat(SSL *s)
{
@@ -2646,7 +2647,7 @@
* sequence number */
n2s(pl, seq);
- if (payload == 18 && seq == s->tlsext_hb_seq)
+ if ((payload == (18 + PAYLOAD_EXTRA)) && seq == s->tlsext_hb_seq)
{
s->tlsext_hb_seq++;
s->tlsext_hb_pending = 0;
@@ -2705,7 +2706,7 @@
/* Message Type */
*p++ = TLS1_HB_REQUEST;
/* Payload length (18 bytes here) */
- s2n(payload, p);
+ s2n(payload + PAYLOAD_EXTRA, p);
/* Sequence number */
s2n(s->tlsext_hb_seq, p);
/* 16 random bytes */
要构建非侵入性测试,请将有效载荷长度增加最多16个字节,即填充长度。当易受攻击的服务器响应此类请求时,它将返回填充,但不会返回其他任何内容。要构建侵入性测试,请将有效载荷长度增加32个字节。易受攻击的服务器将以50字节的有效载荷(默认情况下由OpenSSL发送18字节,加上您的32字节)进行响应,并发送16字节的填充。通过以这种方式增加声明的有效负载长度,易受攻击的服务器将返回高达64KB的数据。不易受Heartbleed攻击的服务器将不会响应。
要生成自己的Heartbleed测试工具,请解压缩OpenSSL源代码的新副本,编辑 ssl/t1_lib.c 以进行补丁中的更改,照常编译,但不要安装。生成的openssl二进制文件将放置在 apps/ 子目录中。因为它是静态编译的,所以您可以将其重命名为类似 openssl-heartleed
的名称,并将其移动到永久位置。
以下是一个易受攻击的服务器返回16字节服务器数据(粗体)的输出示例:
x
B
HEARTBEATING
>>> TLS 1.2 [length 0025], HeartbeatRequest
01 00 32 00 00 7c e8 f5 62 35 03 bb 00 34 19 4d
57 7e f1 e5 90 6e 71 a9 26 85 96 1c c4 2b eb d5
93 e2 d7 bb 5f
<<< TLS 1.2 [length 0045], HeartbeatResponse
02 00 32 00 00 7c e8 f5 62 35 03 bb 00 34 19 4d
57 7e f1 e5 90 6e 71 a9 26 85 96 1c c4 2b eb d5
93 e2 d7 bb 5f 6f 81 0f aa dc e0 47 62 3f 7e dc
60 95 c6 ba df c9 f6 9d 2b c8 66 f8 a5 45 64 0b
d2 f5 3d a9 ad
read R BLOCK
如果您想在单个响应中查看检索到的更多数据,请增加有效载荷长度,重新编译,然后再次测试。或者,要检索另一批相同大小的数据,请再次使用 B
命令。
从OpenSSL 1.0.2开始,当您连接到服务器时,s_client
命令会打印临时Diffie-Hellman密钥的强度(如果使用的话)。因此,要确定服务器DH参数的强度,您需要做的就是连接到它,同时只提供使用DH密钥交换的套件。例如:
xxxxxxxxxx
$ openssl s_client -connect www.feistyduck.com:443 -cipher kEDH
[...]
---No client certificate CA names sent
Peer signing digest: SHA512
Peer signature type: RSA
Server Temp Key: DH, 2048 bits
---
[...]
支持导出套件的服务器实际上可能提供更弱的DH参数。要检查这种可能性,请使用旧的OpenSSL连接,同时只提供导出DHE套件:
xxxxxxxxxx
$ openssl s_client -connect www.feistyduck.com:443 -cipher kEDH+EXPORT
对于配置良好的服务器,此命令应失败。否则,您可能会看到服务器提供协商不安全的512位DH参数。